Skip to content

feat(2245): signed provenance envelope for served tutorials - #2256

Merged
jung-thomas merged 18 commits into
DEVfrom
worktree-provenance-envelope-spec
Sep 11, 2026
Merged

jung-thomas merged 18 commits into
DEVfrom
worktree-provenance-envelope-spec

Conversation

@jung-thomas

Copy link
Copy Markdown
Contributor

Summary

Implements the signed provenance envelope (#2245) — a cryptographically signed freshness/provenance signal for served tutorials. Feature is DB-gated, default OFF, DEV-first, and fail-open everywhere.

What it adds

  • Feature flag PROVENANCE_ENVELOPE_ENABLED — DB config (ImsConfig key flag.provenance.envelope), registered in the feature-flags registry (kind:'db'). Never env.
  • Ed25519 signing via jose (existing dep — no hand-rolled crypto). JWS envelope; public JWKS distribution; kid = JWK thumbprint; private d never leaves the server.
  • Two anonymous read-only Express routes (intentionally not CAP services — @requires/@restrict N/A by design):
    • GET /content/tutorials/:slug/provenance → { jws, jwks_url }
    • GET /.well-known/tutorial-provenance/jwks.json → public JWKS
  • Advisory serve headers X-Freshness-Confidence + X-Content-Provenance on the HTML serve path when the flag is ON (both cache-miss and warm-cache paths gated).
  • sourceCommit SHA threaded fetch → publish → HANA ContentCurrent.sourceCommit (String(64)); migration from cds build --production.
  • Deterministic confidence derivation (no serve-time LLM); latest FreshnessReport via .orderBy('runAt desc').

Security / ops

  • Signing key PROVENANCE_SIGNING_KEY (Ed25519 PKCS8 PEM) lives in the BTP Credential Store, surfaced as runtime env; rotation via /admin-ui/#secrets. No key material in source.
  • Any error → no envelope / no headers; content always serves.
  • srv/lib/ additions audited against the srv-qa cp list in .deploy/mta.yaml; jose present in gen/srv-qa/package.json.

Testing

  • 4 provenance suites pass in isolation (10/10) + headers regression 3/3.
  • cds build --production clean; migration=3 adds sourceCommit NVARCHAR(64).
  • Full suite: 0 assertion failures (file-level failures are pre-existing worktree-infra artifacts — optional native deps not installed in this worktree — orthogonal to this feature).

Docs

Built via subagent-driven development: per-task spec+quality review, whole-branch final review (APPROVED), fix wave for 3 Minor findings.

Closes #2245.

Release 1.25.0 — DEV → main (PROD promotion)
Adds sourceCommit : String(64) to ContentFilesAspect, ContentCurrentAspect,
and ContentHistoryAspect in db/_content-shape.cds. Threads the per-slug
sourceCommits map from appendHandler in content-store.js through
appendToSession in content-publish-session.js, stamping it on the
ContentFiles entry and carrying it forward to ContentCurrent/ContentHistory
in dualWriteCurrentAndHistory (both HANA SQL and SQLite CQL paths).
Generates migration=3 for ContentFiles (ALTER TABLE ADD NVARCHAR(64)).
Exposes GET /content/tutorials/:slug/provenance (signed JWS envelope)
and GET /.well-known/tutorial-provenance/jwks.json (public key set).
Both routes are gated by the PROVENANCE_ENVELOPE_ENABLED DB flag.
Provenance route registered before the *slug wildcard in server.js to
prevent wildcard capture of the /provenance suffix.

globalThis bridge added to provenance-keys.js (__setKeyForTest) to fix
the Windows served-handler module-duplication issue — matches the
established pattern from feature-flags/db-flags.js.

srv-qa cp list updated with all five provenance-*.js files.
Extends the ContentCache to carry per-entry advisory metadata and emits
X-Freshness-Confidence / X-Content-Provenance headers on both the cache-miss
(fresh DB read) and cache-hit branches of serveStoredSlug when the
PROVENANCE_ENVELOPE_ENABLED feature flag is ON. Headers are absent when the
flag is OFF (fail-open: flag check, loadProvenanceInputs error, and null
report all return null advisory → no headers set, serve path unaffected).
Wires ghMeta.lastCommitSha from fetch-tutorials through publish-content
into the append POST body so the server can stamp sourceCommit on each
ContentCurrent row.

- publish-client.ts: extract pure buildAppendBody() helper (unit-testable),
  add sourceCommits to AppendInput; conditionally spreads it into the POST
  body — key absent entirely when no SHAs provided (back-compat)
- fetch-tutorials.ts: write <slug-lowercase>.commit-sha sidecar to CACHE_DIR
  in both the cached and fetched branches after ghMeta is obtained
- publish-content.ts: add buildSourceCommitsPayload() that reads those sidecars,
  build sourceCommitsAll alongside sourcesAll, pass sourceCommits: pickEntries(...)
  in every appendBatch call
- test/unit/publish-content-source-commit.test.js: two cases — includes key
  when SHAs provided; omits key entirely when absent
…velope-spec

# Conflicts:
#	srv/lib/content-store.js
@jung-thomas
jung-thomas merged commit a4ea1ec into DEV Sep 11, 2026
8 checks passed
@jung-thomas
jung-thomas deleted the worktree-provenance-envelope-spec branch September 11, 2026 22:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant