upload current sources
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
# ADR-001 — CAP-first rebuild; Express predecessor demoted to reference
|
||||
|
||||
Date: 2026-09-04 · Status: Accepted — **amended by ADR-002 (2026-09-04)**: the
|
||||
rebuild target is now on-stack RAP + Fiori Elements in the tenant, with a
|
||||
side-by-side connectivity worker; CAP exits the architecture. This ADR's core
|
||||
rationale — the SAP toolchain must engage, and the Express shape is not the
|
||||
pattern — stands unchanged.
|
||||
|
||||
## Context
|
||||
|
||||
The working predecessor (`~/projects/sap-apps/arena-plm`) is Express.js + axios
|
||||
with hand-rolled SAP patterns. It functions, but it sits outside the SAP-native
|
||||
toolchain: the CAP, Fiori, and UI5 MCP servers and skills had nothing to attach
|
||||
to, so development guidance never engaged, and the implementation drifted from
|
||||
SAP best practice — hand-managed CSRF tokens, an approval store in droplet-local
|
||||
SQLite that vanishes on restart, JWT validation with no scope checks. The estate
|
||||
is explore-phase; there is no production cutover to protect, so preserving the
|
||||
legacy shape has no value beyond reference.
|
||||
|
||||
## Decision
|
||||
|
||||
I rebuild arena-plm as a CAP (Node.js) application: the approval domain modeled
|
||||
in CDS, all S/4HANA calls through CAP remote services / SAP Cloud SDK via the
|
||||
`mc-s4hana` destination, and Fiori Elements evaluated before any custom UI5.
|
||||
The Express app is reference-only — consulted for behavior and tombstones,
|
||||
never extended.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The SAP toolchain becomes load-bearing: `cap-mcp-server` is consulted on
|
||||
every model and service change, which is the corrective for the
|
||||
tooling-never-engaged failure mode.
|
||||
- Auth, CSRF, and destination handling move onto framework-managed code paths,
|
||||
eliminating the predecessor's hand-rolled plumbing.
|
||||
- The approval workflow domain must be re-modeled in CDS; behavior parity is
|
||||
checked against the reference app, never assumed from it.
|
||||
- Whether the sync-pipeline half stays inside CAP or becomes a separate
|
||||
hardened worker is deliberately deferred (PRD OQ-4).
|
||||
@@ -0,0 +1,85 @@
|
||||
# ADR-002 — On-stack first: RAP approval domain + Fiori app in the tenant; side-by-side reduced to a connectivity worker
|
||||
|
||||
Date: 2026-09-04 · Status: Accepted (amends ADR-001)
|
||||
|
||||
## Context
|
||||
|
||||
The decisive test came from outside: the showcased UI apps present the data
|
||||
well, but they are invisible from inside S/4HANA — not in the Fiori launchpad,
|
||||
not discoverable in the Custom CDS Views app, reachable only via standalone
|
||||
Cloud Foundry URLs behind an ad-hoc route structure with weak access control.
|
||||
For an S/4HANA user, an application that cannot be found in the tenant does not
|
||||
exist.
|
||||
|
||||
ADR-001 correctly rejected the Express shape but still assumed a side-by-side
|
||||
BTP application (CAP) as the rebuild target. That assumption fails the same
|
||||
test: a CAP app on BTP is still a standalone URL outside the tenant.
|
||||
|
||||
S/4HANA Cloud Public Edition offers two in-tenant extensibility tiers, both
|
||||
verified against SAP documentation (2026-09-04, via sap-docs MCP):
|
||||
|
||||
- **Developer extensibility**: upgrade-stable ABAP Cloud development directly
|
||||
on the stack (ADT against the development tenant) — CDS entities, RAP
|
||||
business objects with behavior, custom Fiori apps deployed into the tenant,
|
||||
authorization via IAM app → business catalog → business role.
|
||||
- **Key-user extensibility**: Custom Business Objects, Custom CDS Views (on
|
||||
released C1 view entities), Custom Analytical Queries, and Business
|
||||
Configuration maintenance via the Custom Business Configurations app.
|
||||
|
||||
The estate's standing rule already says extensibility-first: check what the
|
||||
tenant can host before building side-by-side.
|
||||
|
||||
## Decision
|
||||
|
||||
I move the application into the tenant and shrink the side-by-side footprint to
|
||||
what genuinely cannot run there:
|
||||
|
||||
**On-stack (developer extensibility, ABAP Cloud / RAP):**
|
||||
- A custom RAP business object for the Arena change approval domain — staging
|
||||
data, approval state machine (pending → approved/rejected), audit fields —
|
||||
persisted in the tenant.
|
||||
- A Fiori Elements List Report / Object Page on that BO, deployed to the
|
||||
tenant's Fiori launchpad; approve/reject as RAP actions.
|
||||
- IAM app, business catalog, and business roles as the only access control —
|
||||
no user-facing custom URLs anywhere.
|
||||
- Custom CDS views over the approval BO (joinable with released views such as
|
||||
I_Product) so the data is discoverable in the Custom CDS Views app and usable
|
||||
in analytical queries — the discoverability test that triggered this ADR.
|
||||
- Business mappings evaluated as Business Configuration maintenance objects
|
||||
(Custom Business Configurations app) instead of JSON files in git.
|
||||
|
||||
**Side-by-side (one hardened TypeScript worker, no UI, no user URLs):**
|
||||
- Arena connectivity only: webhook receipt, delta polling, session management
|
||||
(25-min TTL, login race), file streaming.
|
||||
- Initially also the sync executor: on approval, it calls the released OData
|
||||
APIs (Product/BOM/ChangeMaster/Attachment) exactly as the predecessor did —
|
||||
every KNOWLEDGE.md tombstone stays valid unchanged.
|
||||
- It exchanges data with the tenant through the approval BO's service binding
|
||||
exposed via a custom communication scenario/arrangement with a technical
|
||||
user.
|
||||
|
||||
CAP exits the architecture: with the domain, UI, and persistence on-stack, the
|
||||
remaining side-by-side piece is a pipeline, and per the two-archetype standard
|
||||
a pipeline is a worker, not a CAP service. This resolves ADR-001's deferred
|
||||
question and PRD OQ-1 through OQ-4 in one move.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The Fiori launchpad becomes the application's only front door; the shared
|
||||
BTP gateway, XSUAA role juggling, and standalone routes disappear from this
|
||||
package entirely.
|
||||
- Persistence and audit live in the tenant — the predecessor's
|
||||
lost-on-restart approval queue defect is solved structurally, not patched.
|
||||
- The toolchain shifts: ABAP Cloud + RAP + Fiori Elements, so the MCP loadout
|
||||
moves to sap-docs / abap-docs / sap-released-objects / fiori / ui5, and
|
||||
cap-mcp-server leaves the loadout.
|
||||
- Verification items before build (POC gates):
|
||||
- V-1: confirm developer-extensibility access on my429480 (development
|
||||
tenant reachable via ADT, developer business role assigned).
|
||||
- V-2: confirm a custom communication scenario can expose the approval BO's
|
||||
OData service to the worker's technical user.
|
||||
- V-3: evaluate on-stack Application Jobs vs the worker for Arena delta
|
||||
polling; and Business Configuration objects vs config files (ARN-004).
|
||||
- Fallback recorded: if developer extensibility proves unavailable on this
|
||||
tenant, the key-user tier (Custom Business Object with generated UI) is the
|
||||
degraded-but-still-in-tenant variant; side-by-side is no longer the fallback.
|
||||
@@ -0,0 +1,94 @@
|
||||
# ADR-003 — Ground-truth enforcement: hooks, not advice
|
||||
|
||||
Date: 2026-09-04 · Status: Accepted
|
||||
|
||||
## Context
|
||||
|
||||
The old estate proved that advisory guidance fails: an MCP routing table, skills,
|
||||
and operating rules existed for months, yet the servers and skills were never
|
||||
consulted during development — even on the CAP packages where they obviously
|
||||
applied. Causes: tool-selection dilution under a saturated loadout, zero
|
||||
enforcement (prose loses to momentum), friction (cold starts, deferred tool
|
||||
loading), and model overconfidence in trained SAP knowledge. The requirement for
|
||||
this repo: consultation must be structural — the agent must forget what it
|
||||
thinks it knows, consult the MCP servers and context7 before work starts, and be
|
||||
able to cite sources on demand.
|
||||
|
||||
## Decision
|
||||
|
||||
Four mechanisms in `.claude/settings.json` + `.claude/hooks/` (project scope,
|
||||
part of the founding kit for every future package):
|
||||
|
||||
1. **SessionStart injection** — every session opens with the grounding charter:
|
||||
training knowledge is not a citable source; the five MCP servers + context7
|
||||
are; if tools are deferred, loading them is the first action.
|
||||
2. **Per-prompt reflex (UserPromptSubmit)** — a SAP-scoped analogue of the
|
||||
machine-global TOOL-REFLEX hook: never classify whether a prompt "needs" a
|
||||
consult; the classification step is the known failure.
|
||||
3. **Consultation gate (PreToolUse deny)** — every successful `mcp__*`,
|
||||
WebSearch, or WebFetch call stamps `.claude/.last-mcp-consult`; a Write/Edit
|
||||
on a SAP artifact (`.cds`, `.abap`, `.bdef`, annotations, views,
|
||||
`manifest.json`, `srv/`, `app/`, `worker/`) is **denied** unless a
|
||||
consultation happened within 30 minutes, with the remedy stated in the denial
|
||||
reason. Governance `.md`, `docs/`, and `.claude/` paths are exempt so
|
||||
documentation never jams.
|
||||
4. **Citation duty** — every SAP behavioral claim recorded in PRD/ADR/KNOWLEDGE
|
||||
carries `[source: <server> <doc-id/URL>, verified YYYY-MM-DD]` (CLAUDE.md
|
||||
rule 6); a PostToolUse reminder fires on governance-doc edits. Uncited =
|
||||
hypothesis.
|
||||
|
||||
## On "load tools automatically at session start"
|
||||
|
||||
**Corrected 2026-09-04** after reading the 2026-09-03 "Investigate performance
|
||||
lag issue" session transcript: my original claim ("no setting exists to preload
|
||||
deferred tools") was true of settings *keys* but wrong in practice. The
|
||||
`ENABLE_TOOL_SEARCH=false` environment variable disables deferral entirely
|
||||
(verified against the binary in that session). It must be set before process
|
||||
bootstrap — the settings.json `env` block loads too late, per that session's
|
||||
finding — so it lives in the `claude()` wrapper in `~/.bashrc`, where it is
|
||||
**already active machine-wide**, including for sessions started in this repo.
|
||||
History: preloading was first measured at ~98k tokens/turn at the old ~950-tool
|
||||
surface and rejected in favor of the TOOL-REFLEX hook; once plugin localization
|
||||
cut the surface to ~40 tools, the same session adopted preloading via the
|
||||
wrapper. This repo's slim 5-server loadout keeps the preload cost small.
|
||||
Mechanism 1 (SessionStart load-first order) remains as the fallback for launch
|
||||
paths that bypass the wrapper (e.g. IDE-spawned sessions).
|
||||
|
||||
## Consequences
|
||||
|
||||
- Consultation stops being a virtue and becomes a precondition; skipping it now
|
||||
produces a denied edit instead of silent drift.
|
||||
- The 30-minute freshness window is a tunable; if it proves too tight or too
|
||||
loose in practice, adjust it in `sap-consult-gate.sh` and note it here.
|
||||
- The gate measures that *a* consultation happened, not that the *right* one
|
||||
did — citation duty and review cover relevance.
|
||||
- Hooks activate on the first session started inside this repo (project hooks
|
||||
are read at session start).
|
||||
|
||||
## Amendment 2026-09-14 — gate hardened after code review
|
||||
|
||||
The 2026-09-04 gate was advisory in practice: the `Write|Edit` matcher missed
|
||||
Bash writes (sed -i, heredocs, tee, cp/mv, interpreter one-liners), MultiEdit,
|
||||
NotebookEdit and filesystem-MCP edits; the `mcp__.*` PostToolUse matcher let
|
||||
any MCP call (memory, chrome, mcp-manage) refresh the 30-minute marker; hook
|
||||
commands hard-coded this checkout's absolute path; and a missing `jq` produced
|
||||
a silent allow. I changed the mechanism, not the prose:
|
||||
|
||||
- PreToolUse gate matches `Write|Edit|MultiEdit|NotebookEdit|Bash|
|
||||
mcp__filesystem__write_file|mcp__filesystem__edit_file|mcp__filesystem__move_file`;
|
||||
for Bash the script gates only commands that combine a write indicator with
|
||||
a gated path (documented heuristic, known blind spots: variable-built paths,
|
||||
`cd`-relative paths, writes by invoked scripts).
|
||||
- The consult marker is refreshed only by the grounding servers (sap-docs,
|
||||
abap-docs, sap-released-objects, the Fiori/UI5 servers, context7, CAP) and
|
||||
WebSearch/WebFetch — anchored regex in `settings.json`.
|
||||
- Hook commands resolve via `"${CLAUDE_PROJECT_DIR}"`; scripts prefer that
|
||||
variable over `$0` [source: https://code.claude.com/docs/en/hooks —
|
||||
"`${CLAUDE_PROJECT_DIR}`: the project root where the session started",
|
||||
verified 2026-09-14].
|
||||
- Missing `jq` or an unresolvable marker now denies (fail closed); `jq` is a
|
||||
hard dependency of any session in this repo.
|
||||
- `*/worker/*` left the gated set: the TypeScript worker is governed by
|
||||
context7 and the OpenAPI contract, not the SAP gate.
|
||||
- `.claude/hooks/tests/run.sh` pipe-tests both scripts (37 cases) against an
|
||||
override marker so the real marker is never touched.
|
||||
Reference in New Issue
Block a user