Skip to content

feat(tool-capability): persist external-extension lifecycle evidence - #574

Merged
seonghobae merged 49 commits into
mainfrom
feat/external-extension-lifecycle-ledger
Sep 9, 2026
Merged

feat(tool-capability): persist external-extension lifecycle evidence#574
seonghobae merged 49 commits into
mainfrom
feat/external-extension-lifecycle-ledger

Conversation

@seonghobae

@seonghobae seonghobae commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Noema Tool Capability / State / Checkpoint lifecycle evidence

This Draft owns only Noema external-extension lifecycle authority: transition/version/head state and immutable Noema Policy / Approval plus AppGuardrail, quarantine/isolation, and Egress references/digests. It does not copy foreign scanner/runtime/outbound truth or contextual-orchestrator provider/model routing into Noema.

Reality RED → causal repairs

The branch preserves fail-closed lifecycle/state invariants through hostile findings and minimal repairs:

  • invalid same-ID conflict fixture repaired without weakening lifecycle validation;
  • exact committed activation replay never reconsults later mutable owner evidence;
  • transaction-time replay loser returns only a candidate and is cryptographically reverified after the short storage transaction;
  • readCurrent() verifies compact head + exact tail in O(1) storage cardinality while complete prefix verification remains audit/recovery work;
  • malformed/truncated/tampered heads/tails cannot be extended into new authority;
  • NEW append verifies current projection before event construction and CAS-checks expected version/state/head inside the transaction;
  • activation idempotency race returns replay only for the exact already-committed request after immutable digest/head/tail verification;
  • approval and effective product/role scope are canonical SHA-256 identities derived from the current Noema approval; forged/narrowed/drifted approval or scope fails closed;
  • persisted scope identity uses deterministic ECMAScript default string ordering, never locale-sensitive collation;
  • approval windows and lifecycle occurred_at values must round-trip to exact canonical UTC instants before they can become immutable authority.

Historical exact-head review findings remain preserved in the review timeline. The latest causal source repair before reconvergence was 0e7b24ddad4f16e378f22d6b16ff7cbe842a8aa1, which rejects impossible-but-parseable lifecycle occurrence instants without weakening replay or foreign-owner evidence checks.

Current exact authority

Protected main advanced through the independent hourly-lane and agent-handoff changes without touching this PR's 21 owned paths. This lane therefore ordinary/non-force reconverged by a two-parent merge onto protected main@f83d42817ac90aad4159e7b8649eed5b9a6f8740. The post-reconvergence compare is ahead-only and contains exactly the same 21 lifecycle files, with no protected-main path lost.

Current exact is f3c4343a3d764fe380c0f9ad5b26fb108e4960b5. All predecessor-head GREEN is invalidated by the new merge commit. Keep Draft until application CI, reviewer-ci, Security Scan, and patch-validator-image are all GREEN on this unchanged exact and fresh review/base remain clean.

Canonical documentation / operability

The candidate keeps lifecycle treatment aligned through docs/CONTEXT_MAP.md, Proposed ADR 0015, docs/TEST_STRATEGY.md, docs/OPERABILITY.md, docs/TRACEABILITY.md, docs/external-extension-lifecycle-recovery.md, docs/PRD.md FR-023, docs/TRD.md, root ARCHITECTURE.md, and docs/UML.md. docs/product-technical-gap-baseline.md is not silently reconstructed from partial bytes; issue #5 remains its dedicated authority.

Issue #561 still requires realistic actual Durable Object current-projection and contended-append p95 <= 20 ms where synchronous, partition/lock/storage-growth evidence, snapshot/audit rebuild and recovery rehearsal. Unit-test timing and the O(1) algorithmic bound do not satisfy that operational acceptance. Source integration remains separate from immutable release, deployment, pilot and production KPI authority.

Summary by CodeRabbit

  • 새 기능

    • 외부 확장의 발견부터 활성화, 중지, 대체, 거부, 만료까지 수명주기를 내구성 있게 기록하고 재시작 후 복원합니다.
    • 활성화 전에 최신 정책 승인과 보안·격리 증거를 재검증하며, 불일치 시 안전하게 거부합니다.
    • 동시 요청과 중복 요청을 안전하게 처리하고 감사 이력을 보존합니다.
  • 문서

    • 외부 확장 수명주기의 운영, 복구, 저장, 추적성 및 아키텍처 가이드를 추가·갱신했습니다.
  • 테스트

    • 재시작 복구, 동시성, 재생, 데이터 손상, 증거 변경 및 시간 유효성 검증을 추가했습니다.

@coderabbitai

coderabbitai Bot commented Sep 9, 2026

Copy link
Copy Markdown

Review Change StackReview Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 940913d8-da43-4065-a17e-cc00fc365ba6

📥 Commits

Reviewing files that changed from the base of the PR and between f83d428 and f3c4343.

📒 Files selected for processing (21)
  • ARCHITECTURE.md
  • docs/CONTEXT_MAP.md
  • docs/OPERABILITY.md
  • docs/PRD.md
  • docs/TEST_STRATEGY.md
  • docs/TRACEABILITY.md
  • docs/TRD.md
  • docs/UML.md
  • docs/adr/0015-external-extension-admission.md
  • docs/external-extension-lifecycle-recovery.md
  • src/tool-capability/external-extension-lifecycle-evidence.ts
  • src/tool-capability/external-extension-lifecycle-store.ts
  • test/external-extension-lifecycle-activation-race.test.ts
  • test/external-extension-lifecycle-approval-binding.test.ts
  • test/external-extension-lifecycle-evidence.test.ts
  • test/external-extension-lifecycle-head-integrity.test.ts
  • test/external-extension-lifecycle-projection.test.ts
  • test/external-extension-lifecycle-replay-integrity.test.ts
  • test/external-extension-lifecycle-replay.test.ts
  • test/external-extension-lifecycle-store.test.ts
  • test/external-extension-lifecycle-time-validation.test.ts

📝 Walkthrough

Walkthrough

외부 확장 라이프사이클의 증거 검증기와 Durable Object 기반 append-only 저장소를 추가했습니다. CAS, 멱등 replay, 감사 체인, current projection, 손상 감지와 복구 계약을 구현하고 관련 테스트와 아키텍처·운영 문서를 갱신했습니다.

Changes

외부 확장 라이프사이클

Layer / File(s) Summary
라이프사이클 계약과 저장소 구현
src/tool-capability/external-extension-lifecycle-store.ts, src/tool-capability/external-extension-lifecycle-evidence.ts
상태 전이, 스트림 식별자, Policy/Approval 및 scan receipt 검증, SHA-256 증거 바인딩, CAS append, 멱등 replay, current projection과 audit 검증을 추가했습니다.
저장소 무결성과 경쟁 검증
test/external-extension-lifecycle-*.test.ts
재시작 복구, CAS 경쟁, activation evidence drift, 동일 transition replay, head·tail 손상, cross-stream 상태, 금지 데이터 비영속화와 잘못된 timestamp를 검증합니다.
아키텍처 및 기술 계약
ARCHITECTURE.md, docs/CONTEXT_MAP.md, docs/PRD.md, docs/TRD.md, docs/UML.md, docs/TRACEABILITY.md, docs/adr/0015-external-extension-admission.md
라이프사이클 aggregate의 저장 경계, 상태 흐름, evidence reference, append-only 감사 체인, projection과 recovery 경로를 문서화했습니다.
운영 및 복구 절차
docs/OPERABILITY.md, docs/external-extension-lifecycle-recovery.md
Durable Object 배포, 관측성, p95 측정 조건, rollback·suspension, 보존, corruption 대응과 restart·audit 복구 절차를 정의했습니다.
테스트 전략과 수용 기준
docs/TEST_STRATEGY.md
canonicalization, digest, CAS, replay, evidence drift, 저장소 성장, projection 검증과 복구 rehearsal의 테스트 요구사항을 추가했습니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant LifecycleRepository
  participant EvidenceVerifier
  participant ForeignAuthority
  participant DurableObjectStorage
  Caller->>LifecycleRepository: active 전이 append
  LifecycleRepository->>EvidenceVerifier: 현재 증거 검증
  EvidenceVerifier->>ForeignAuthority: Policy/Approval 및 receipt 조회
  ForeignAuthority-->>EvidenceVerifier: 참조와 digest 반환
  EvidenceVerifier-->>LifecycleRepository: 검증 결과
  LifecycleRepository->>DurableObjectStorage: CAS로 event, index, head 기록
  DurableObjectStorage-->>LifecycleRepository: 커밋 결과
  LifecycleRepository-->>Caller: accepted 또는 replay 반환
Loading
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/external-extension-lifecycle-ledger

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact-head review finding on 2d7ea9a118a4768cbbc11df680f7b6a46a5f644a: the new preflight replay integrity checks do not protect the transaction-time loser path. If preflight sees no transition index, then another writer commits (or durable evidence is corrupted) before this transaction acquires the serialization point, the existingIndex !== undefined branch returns existingEvent after structural field checks only. It does not recompute requestHashMaterial(existingEvent) / eventHashMaterial(existingEvent) or verify the current audit tail, so this race can return evidence that the normal readExistingReplay() path would reject. Keep Web Crypto outside the short storage transaction as #561 requires: have the transaction return a detached replay candidate/reference, then cryptographically validate that candidate and head/tail after the transaction before returning kind: replay (or equivalently re-enter the existing replay verifier without re-reading mutable Policy/Approval evidence). Add a deterministic hostile race where preflight misses the index, a competing commit/tamper lands, the transaction sees the index, and a request/event digest or tail mismatch fails closed. Do not weaken the 100% coverage gate; current application CI 34348143585 is already RED in release tests, so the repair should make both this authority gap and the exact-head coverage failure GREEN.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up exact-head finding on c3731898ab1af7c9cdd2acff51b9c054328794f2: the post-transaction replay verifier closes the digest/tail race from my prior review, but NEW active requests still have a narrower idempotency race between the initial readExistingReplay() miss and assertCurrentActivationEvidence(). If writer A commits this exact transition after writer B's preflight miss, and Policy/Approval or owner evidence is revoked before B's live verifier returns, B throws ExternalExtensionLifecycleEvidenceError even though the exact transition ID + request digest is already durably committed. That contradicts #561 invariants that exact duplicates are idempotent replay and historical committed events remain evidence rather than live authority. Preserve fail-closed activation for genuinely new transitions: on activation-evidence failure, re-run the immutable replay check before propagating the evidence error; return replay only if the exact committed request now exists and passes full digest/head/tail verification, otherwise throw the original evidence error. Add a deterministic barrier regression for preflight miss → competing exact commit → evidence revocation/failure → immutable replay. Do not move foreign-owner verification into the storage transaction or weaken fresh-evidence requirements for the winner.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocking finding on exact 74b55559efa85ec44245ed25442d0db184945889: the NEW active evidence verifier still does not bind the activation event to the approval's product/role scope or to an exact approval identity. TrustedExtensionPolicyApproval carries allowed_product_repositories and allowed_execution_roles, but assertApprovalCurrent() never checks them; the hostile test that claims to vary every authority-bearing approval field omits both. A live approval may therefore narrow repository/role scope immediately before append and the activation can still be accepted. Separately, policy_approval_reference and effective_scope_reference are persisted request URNs but are not derived from or checked against the approval object returned by resolvePolicyApproval(extensionId), so the append-only audit event can name an arbitrary syntactically valid approval/scope reference while revalidating a different current approval.

This violates #561 invariants 4/5 and its buyer requirement to prove which exact Noema Policy/Approval plus effective product/role scope caused each activation. Keep foreign-owner truth as references/digests; the repair belongs in Noema's approval port: expose/bind a canonical approval identity and canonical effective-scope identity (or exact scope digest derived from the approved repo/role sets), add REDs for repo-scope drift, role-scope drift, forged approval reference and forged scope reference, then make NEW activation fail closed on any mismatch. Exact committed replay should continue using immutable stored evidence without reconsulting mutable authority.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Second blocking recovery finding on the same exact: readAudit() verifies every event's stream/hash chain and then checks only head.version/state/head_event_sha256 against the last event. It does not verify head.schema_version or head.stream against the requested stream/tail. A persisted snapshot whose stream identity is corrupted/substituted can therefore pass the full audit/recovery path even though readCurrent() and replay verification would reject it. #561 explicitly requires malformed persisted snapshots and forged/truncated snapshot-event-prefix continuity to fail closed. Add a hostile RED that tampers only the persisted head stream (and one for head schema version), then make readAudit() enforce the same exact head schema/stream binding before treating the chain as recovery evidence. This should remain O(n) only on audit/recovery; do not move full-prefix work back into readCurrent().

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fresh exact-head finding on 7cae6b6c5a62a614cf35e40fa0645623651ae28f: AuthorityBackedExternalExtensionLifecycleEvidenceVerifier.assertApprovalCurrent() trusts Date.parse() + finiteness for valid_from / valid_to, while the canonical external-extension admission boundary already requires an exact UTC instant by round-tripping new Date(parsed).toISOString() === input. ECMAScript normalizes impossible calendar dates such as 2026-09-31T11:00:00.000Z to October 1, so a Noema Policy/Approval can carry impossible/non-canonical authority time bytes, derive a matching approval SHA-256 reference from those bytes, and still be treated as live. That weakens #561's requirement that the exact Noema Policy/Approval and validity window be reproducible authority. Add a hostile RED using a request reference computed from an impossible-but-parseable approval timestamp, then make the lifecycle evidence port reject any approval time that is not a real canonical UTC instant. Keep exact committed replay semantics unchanged and do not weaken the existing approval-reference binding.

@seonghobae seonghobae left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fresh exact-head review found one Noema-owned validation gap in the lifecycle ledger. The approval window now rejects impossible-but-parseable UTC instants, but the event occurred_at path still accepts them. Keep Draft and repair with a hostile regression plus the same canonical UTC round-trip invariant; do not weaken lifecycle semantics or foreign-owner evidence checks.

Comment thread src/tool-capability/external-extension-lifecycle-store.ts
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