upload current sources

This commit is contained in:
2026-09-16 01:47:28 -07:00
commit f8b45ea4a5
109 changed files with 26012 additions and 0 deletions
+38
View File
@@ -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).
+85
View File
@@ -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.