Skip to content
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
49 commits
Select commit Hold shift + click to select a range
501821d
test(tool-capability): define durable lifecycle ledger hostile cases
seonghobae Sep 9, 2026
a79e798
feat(tool-capability): persist lifecycle evidence with CAS
seonghobae Sep 9, 2026
ed89403
test(tool-capability): isolate transition-id semantic conflict
seonghobae Sep 9, 2026
d78aa8f
test(tool-capability): cover durable-ledger corruption branches
seonghobae Sep 9, 2026
988b077
feat(tool-capability): revalidate activation evidence before lifecycl…
seonghobae Sep 9, 2026
1fcdf6a
test(tool-capability): enforce fresh activation evidence boundary
seonghobae Sep 9, 2026
df56960
feat(tool-capability): bridge lifecycle activation to live owner evid…
seonghobae Sep 9, 2026
54aa141
test(tool-capability): cover live activation evidence revalidation
seonghobae Sep 9, 2026
c837025
test(tool-capability): expose activation replay revalidation bug
seonghobae Sep 9, 2026
ef675b4
fix(tool-capability): replay committed activation before live evidenc…
seonghobae Sep 9, 2026
4ee509f
test(tool-capability): cover transactional idempotency races
seonghobae Sep 9, 2026
48da6aa
test(tool-capability): expose replay integrity gaps
seonghobae Sep 9, 2026
2d7ea9a
fix(tool-capability): verify and replay immutable lifecycle evidence
seonghobae Sep 9, 2026
2594cfd
test(tool-capability): cover missing replay audit tail
seonghobae Sep 9, 2026
67300ff
test(tool-capability): cover transactional replay integrity
seonghobae Sep 9, 2026
16b3b0f
test(tool-capability): expose transaction-time replay digest race
seonghobae Sep 9, 2026
c373189
fix(tool-capability): verify replay after serialized transaction
seonghobae Sep 9, 2026
10630aa
test(tool-capability): expose O(n) current projection scan
seonghobae Sep 9, 2026
0a49f5b
fix(tool-capability): keep current projection O(1)
seonghobae Sep 9, 2026
3225766
test(tool-capability): cover missing projection tail
seonghobae Sep 9, 2026
5c61dc1
test(tool-capability): close replay coverage gaps
seonghobae Sep 9, 2026
fbe9d01
test(tool-capability): expose activation evidence replay race
seonghobae Sep 9, 2026
74b5555
fix(tool-capability): replay exact activation after evidence race
seonghobae Sep 9, 2026
4f1fb30
docs(context-map): bind lifecycle evidence to Noema boundaries
seonghobae Sep 9, 2026
7838afc
docs(adr): define durable external-extension lifecycle evidence
seonghobae Sep 9, 2026
fa69d84
test(tool-capability): reject forged lifecycle head authority
seonghobae Sep 9, 2026
5c4a323
docs(test): cover durable lifecycle evidence races and recovery
seonghobae Sep 9, 2026
925ea90
docs(operability): define lifecycle recovery and p95 evidence
seonghobae Sep 9, 2026
781f931
fix(tool-capability): verify durable head before lifecycle append
seonghobae Sep 9, 2026
562d52c
test(tool-capability): bind activation to exact approval scope
seonghobae Sep 9, 2026
dd331d8
fix(tool-capability): bind activation to exact approval scope
seonghobae Sep 9, 2026
510f419
test(tool-capability): use canonical approval evidence identities
seonghobae Sep 9, 2026
6ac6069
test(tool-capability): reject forged approval and scope references
seonghobae Sep 9, 2026
5c441bf
docs(traceability): bind external-extension lifecycle evidence
seonghobae Sep 9, 2026
6b9d51f
docs(recovery): define external-extension lifecycle recovery authority
seonghobae Sep 9, 2026
fe3fd5e
docs(prd): make lifecycle evidence requirements code-current
seonghobae Sep 9, 2026
fb88498
docs(trd): specify durable extension lifecycle mechanics
seonghobae Sep 9, 2026
6906d71
docs(architecture): map lifecycle evidence recovery boundary
seonghobae Sep 9, 2026
0af817b
docs(uml): model lifecycle append and recovery flows
seonghobae Sep 9, 2026
f1461bb
merge protected main after #566 into external extension lifecycle lane
seonghobae Sep 9, 2026
71f5788
test(tool-capability): cover malformed approval scope evidence
seonghobae Sep 9, 2026
589cbf6
test(tool-capability): pin canonical scope byte ordering
seonghobae Sep 9, 2026
6b62b12
fix(tool-capability): make scope digests runtime-independent
seonghobae Sep 9, 2026
7cae6b6
docs(tool-capability): specify canonical scope digest ordering
seonghobae Sep 9, 2026
85c2d7d
test(tool-capability): reject impossible approval instants
seonghobae Sep 9, 2026
53b04ce
fix(tool-capability): require canonical approval instants
seonghobae Sep 9, 2026
cab250b
test(tool-capability): reject impossible lifecycle occurrence instants
seonghobae Sep 9, 2026
0e7b24d
fix(tool-capability): reject impossible lifecycle occurrence instants
seonghobae Sep 9, 2026
f3c4343
Merge protected main into external-extension lifecycle lane
seonghobae Sep 9, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
140 changes: 140 additions & 0 deletions src/tool-capability/external-extension-lifecycle-evidence.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import type {
ExternalExtensionAuthority,
TrustedExtensionPolicyApproval,
TrustedExtensionScanReceipt,
} from "./external-extension-admission";
import {
ExternalExtensionLifecycleEvidenceError,
type ExternalExtensionLifecycleAppend,
type ExternalExtensionLifecycleEvidenceVerifier,
} from "./external-extension-lifecycle-store";

/** Clock port used only to evaluate the live Noema Policy/Approval validity window. */
export type ExternalExtensionLifecycleClock = () => number;

function rejectEvidence(message: string): never {
throw new ExternalExtensionLifecycleEvidenceError(message);
}

function resolveApproval(
authority: ExternalExtensionAuthority,
request: Readonly<ExternalExtensionLifecycleAppend>,
): TrustedExtensionPolicyApproval {
const resolver = authority.resolvePolicyApproval;
if (resolver === undefined) {
return rejectEvidence("Noema Policy/Approval resolver is unavailable at activation time");
}
let approval: TrustedExtensionPolicyApproval | null;
try {
approval = resolver.call(authority, request.stream.external_extension_id);
} catch {
return rejectEvidence("Noema Policy/Approval lookup failed at activation time");
}
if (approval === null) {
return rejectEvidence("Noema Policy/Approval evidence was revoked before activation");
}
return approval;
}

function resolveReceipt(
authority: ExternalExtensionAuthority,
receiptId: string,
producer: TrustedExtensionScanReceipt["producer"],
): TrustedExtensionScanReceipt {
let receipt: TrustedExtensionScanReceipt | null;
try {
receipt = authority.resolveScanReceipt(receiptId);
} catch {
return rejectEvidence(`${producer} evidence lookup failed at activation time`);
}
if (receipt === null || receipt.producer !== producer || receipt.receipt_id !== receiptId) {
return rejectEvidence(`${producer} evidence was revoked or changed before activation`);
}
return receipt;
}

function assertApprovalCurrent(
request: Readonly<ExternalExtensionLifecycleAppend>,
approval: TrustedExtensionPolicyApproval,
now: number,
): void {
const validFrom = Date.parse(approval.valid_from);
const validTo = Date.parse(approval.valid_to);
const matches = [
approval.external_extension_id === request.stream.external_extension_id,
approval.max_approval_status === "active",
approval.activation_policy_version === request.activation_policy_version,
approval.isolation_profile_reference === request.isolation_profile_reference,
approval.egress_policy_reference === request.egress_policy_reference,
approval.appguardrail_policy_profile_id === request.appguardrail_profile_identity,
approval.appguardrail_policy_profile_sha256 === request.appguardrail_profile_sha256,
approval.quarantine_policy_profile_id === request.quarantine_profile_identity,
approval.quarantine_policy_profile_sha256 === request.quarantine_profile_sha256,
Number.isFinite(validFrom),
Number.isFinite(validTo),
validFrom <= now,
now < validTo,
];
if (matches.includes(false)) {
rejectEvidence("Noema Policy/Approval evidence expired or drifted before activation");
}
}

function assertReceiptCurrent(
request: Readonly<ExternalExtensionLifecycleAppend>,
receipt: TrustedExtensionScanReceipt,
expectedProfileIdentity: string,
expectedProfileSha256: string,
): void {
if (
receipt.artifact_sha256 !== request.stream.artifact_sha256 ||
receipt.policy_version !== request.isolation_profile_reference ||
receipt.policy_profile_id !== expectedProfileIdentity ||
receipt.policy_profile_sha256 !== expectedProfileSha256
) {
rejectEvidence(`${receipt.producer} evidence drifted before activation`);
}
}

/**
* Revalidates activation evidence through the existing owner ports instead of
* persisting or reimplementing AppGuardrail, quarantine, isolation, or egress truth.
*/
export class AuthorityBackedExternalExtensionLifecycleEvidenceVerifier
implements ExternalExtensionLifecycleEvidenceVerifier {
constructor(
private readonly authority: ExternalExtensionAuthority,
private readonly clock: ExternalExtensionLifecycleClock = Date.now,
) {}

async assertCurrentActivationEvidence(
request: Readonly<ExternalExtensionLifecycleAppend>,
): Promise<void> {
const approval = resolveApproval(this.authority, request);
assertApprovalCurrent(request, approval, this.clock());

const appguardrail = resolveReceipt(
this.authority,
request.appguardrail_evidence_reference,
"appguardrail",
);
assertReceiptCurrent(
request,
appguardrail,
request.appguardrail_profile_identity,
request.appguardrail_profile_sha256,
);

const quarantine = resolveReceipt(
this.authority,
request.quarantine_evidence_reference,
"quarantine-sandbox-runtime",
);
assertReceiptCurrent(
request,
quarantine,
request.quarantine_profile_identity,
request.quarantine_profile_sha256,
);
}
}
Loading
Loading