@sema-agent/core 5.21.1 → 5.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +56 -0
- package/dist/agents/send-message-tool.js +6 -3
- package/dist/agents/subagent.d.ts +6 -0
- package/dist/agents/subagent.js +45 -4
- package/dist/brain/errors.d.ts +20 -0
- package/dist/brain/errors.js +40 -0
- package/dist/brain/retry.d.ts +16 -2
- package/dist/brain/retry.js +3 -2
- package/dist/brain/status-sink.d.ts +9 -2
- package/dist/brain/stream-engine.d.ts +22 -0
- package/dist/brain/stream-engine.js +41 -10
- package/dist/core/ask-class.d.ts +48 -0
- package/dist/core/ask-class.js +33 -0
- package/dist/core/checkpoint-store.d.ts +103 -10
- package/dist/core/checkpoint-store.js +3 -1
- package/dist/core/governance-codes.d.ts +38 -0
- package/dist/core/governance-codes.js +11 -0
- package/dist/core/hooks.d.ts +39 -0
- package/dist/core/hooks.js +26 -2
- package/dist/core/locked-config.d.ts +7 -1
- package/dist/core/locked-config.js +2 -1
- package/dist/core/memory-engine/delegation-provenance.d.ts +62 -0
- package/dist/core/memory-engine/delegation-provenance.js +26 -0
- package/dist/core/memory-engine/engine.d.ts +67 -1
- package/dist/core/memory-engine/engine.js +270 -12
- package/dist/core/memory-engine/header-hints.d.ts +30 -0
- package/dist/core/memory-engine/header-hints.js +41 -0
- package/dist/core/memory-engine/index.d.ts +3 -2
- package/dist/core/memory-engine/index.js +3 -2
- package/dist/core/memory-engine/layout.d.ts +166 -0
- package/dist/core/memory-engine/layout.js +399 -0
- package/dist/core/memory-engine/tools.d.ts +30 -0
- package/dist/core/memory-engine/tools.js +108 -17
- package/dist/core/permission-rule-consent.d.ts +25 -9
- package/dist/core/permission-rule-consent.js +91 -20
- package/dist/core/permission-rule-model.d.ts +9 -1
- package/dist/core/permission-rule-model.js +2 -2
- package/dist/core/permission-rule-org.d.ts +161 -0
- package/dist/core/permission-rule-org.js +211 -0
- package/dist/core/permission-rule-store.d.ts +249 -6
- package/dist/core/permission-rule-store.js +313 -3
- package/dist/core/permission-rule-sync.d.ts +131 -0
- package/dist/core/permission-rule-sync.js +314 -0
- package/dist/core/runner/prepare-memory.js +35 -8
- package/dist/core/runner/prepare-task.d.ts +54 -1
- package/dist/core/runner/prepare-task.js +246 -27
- package/dist/core/runner/runtask.js +147 -6
- package/dist/core/shared-memory/contract.js +19 -4
- package/dist/core/shared-memory/normalize.d.ts +3 -1
- package/dist/core/shared-memory/tools.js +73 -17
- package/dist/core/shared-memory/types.d.ts +27 -1
- package/dist/core/store-contracts/permission-rule-sync-contract.d.ts +33 -0
- package/dist/core/store-contracts/permission-rule-sync-contract.js +186 -0
- package/dist/core/task-notification.d.ts +5 -2
- package/dist/core/task-registry-agent.d.ts +1 -1
- package/dist/core/task-registry-agent.js +6 -2
- package/dist/core/task-registry-shared.d.ts +9 -2
- package/dist/core/task-registry.d.ts +9 -3
- package/dist/core/task-registry.js +2 -0
- package/dist/core/tool-policy.d.ts +120 -2
- package/dist/core/tool-policy.js +116 -6
- package/dist/core/trace.d.ts +32 -1
- package/dist/core/types.d.ts +56 -3
- package/dist/index.d.ts +12 -7
- package/dist/index.js +10 -5
- package/dist/stores/file/checkpoint-store.d.ts +4 -0
- package/dist/stores/file/checkpoint-store.js +1 -0
- package/dist/stores/file/permission-rule-adopt.d.ts +62 -0
- package/dist/stores/file/permission-rule-adopt.js +95 -0
- package/dist/stores/file/permission-rule-store.d.ts +80 -2
- package/dist/stores/file/permission-rule-store.js +189 -46
- package/package.json +1 -1
|
@@ -774,6 +774,25 @@ export interface CheckpointState {
|
|
|
774
774
|
* (`resume.parent_constraint_mismatch`) — a partial re-supply is a typed error, never a silently
|
|
775
775
|
* thinner chain. Absent on older checkpoints ⇒ only the non-empty check applies. */
|
|
776
776
|
parentConstraintCount?: number;
|
|
777
|
+
/**
|
|
778
|
+
* F-012 L1 — the persisted CONSTRAINT PROJECTIONS of the inherited chain, one ordered entry per
|
|
779
|
+
* `parentConstraints` layer: the layer's deterministic deny data
|
|
780
|
+
* ({@link import("./tool-policy.js").ToolPolicyProjection} content) or an explicit `{opaque:true}`
|
|
781
|
+
* placeholder for a layer that exports none. The resume leg EXECUTES these persisted projections
|
|
782
|
+
* (extra deny-narrowing layers + the pre-CAS edit re-adjudication) rather than trusting a
|
|
783
|
+
* re-supplied policy's execution half for the frozen deny data — a re-supplied closure that
|
|
784
|
+
* matches the digest but behaves differently cannot un-deny what the suspend froze. Present iff
|
|
785
|
+
* `requiresParentConstraint` on new mints; absent on older checkpoints (⇒ no frozen deny data —
|
|
786
|
+
* the count-only contract governs, and an EDIT resume is refused for want of a validatable chain). */
|
|
787
|
+
constraintChain?: ReadonlyArray<import("./tool-policy.js").ConstraintChainEntry>;
|
|
788
|
+
/**
|
|
789
|
+
* F-012 L1 — the content digest over `constraintChain` ({@link import("./tool-policy.js").constraintChainDigest}):
|
|
790
|
+
* binds the ordered chain topology (layer boundaries + opaque placeholders) and each projection's
|
|
791
|
+
* content. The resume pre-CAS ladder recomputes the digest over the RE-SUPPLIED chain's
|
|
792
|
+
* projections and rejects a mismatch (`resume.parent_constraint_mismatch`) — replacing the
|
|
793
|
+
* count-only shape check with a content contract (a same-length chain of different frozen deny
|
|
794
|
+
* data no longer passes). Present iff `constraintChain` is. */
|
|
795
|
+
constraintDigest?: string;
|
|
777
796
|
};
|
|
778
797
|
/**
|
|
779
798
|
* [c209-C] Q5 — the listing frames' announced NAME-SETS at suspend (agent types / skills the model
|
|
@@ -799,6 +818,16 @@ export interface CheckpointState {
|
|
|
799
818
|
skills?: string[];
|
|
800
819
|
models?: string[];
|
|
801
820
|
};
|
|
821
|
+
/**
|
|
822
|
+
* design/180 R-4 (R-2 cross-process variant) — the armed delegation child's MONOTONIC runtime-provenance
|
|
823
|
+
* aggregate at suspend ({@link import("./memory-engine/delegation-provenance.js").DelegationProvenanceAggregate}:
|
|
824
|
+
* version + coverageStartTurn + the two never-cleared bits sawExternal/incomplete). Persisted so a
|
|
825
|
+
* resume — a cross-process one especially, which lost the in-memory ref — CONTINUES accumulating from
|
|
826
|
+
* the recorded bits instead of restarting a fresh aggregate that could prove "clean" on the post-resume
|
|
827
|
+
* segment alone. Plain data → JSON/structuredClone round-trips. Present ONLY when the suspended leg ran
|
|
828
|
+
* with a recorder armed; ABSENT on an unarmed leg AND on an older checkpoint — the resume side reads
|
|
829
|
+
* absence-on-an-armed-child fail-closed as `incomplete`. */
|
|
830
|
+
delegationProvenance?: import("./memory-engine/delegation-provenance.js").DelegationProvenanceAggregate;
|
|
802
831
|
}
|
|
803
832
|
/**
|
|
804
833
|
* {@link CheckpointState} with **every** field made required-PRESENT, while each value keeps its original
|
|
@@ -867,13 +896,26 @@ export declare const TOKEN_CHECKPOINT_VERSION = 5;
|
|
|
867
896
|
* memory see zero version movement.
|
|
868
897
|
*/
|
|
869
898
|
export declare const ORG_ADMISSION_CHECKPOINT_VERSION = 6;
|
|
899
|
+
/**
|
|
900
|
+
* F-012 (2026-08-09 — the ORG_ADMISSION_CHECKPOINT_VERSION precedent replayed a fifth time, codex
|
|
901
|
+
* adversarial finding): the schema version a suspend stamps when its state carries the F-012 durable
|
|
902
|
+
* enforcement fields — `inheritedGate.constraintChain`/`constraintDigest` (the frozen ancestor
|
|
903
|
+
* projection chain the resume-edit re-adjudication executes) or `delegationProvenance` (the monotonic
|
|
904
|
+
* provenance aggregate a resume re-seeds). Both enforcements live ENTIRELY in the resuming worker: a
|
|
905
|
+
* pre-F-012 worker (MAX_SUPPORTED=6) would accept the row, ignore constraintChain (so an approver-edited
|
|
906
|
+
* resume executes WITHOUT the frozen ancestor deny), and restart the aggregate clean (false-clean after
|
|
907
|
+
* pre-suspend external activity). Stamping v7 forces such a worker to reject PRE-CAS
|
|
908
|
+
* (`unsupported_version`, stays `pending`, retried on an enforcing worker). A checkpoint with NO F-012
|
|
909
|
+
* state keeps the historic stamps — deployments not delegating see zero version movement.
|
|
910
|
+
*/
|
|
911
|
+
export declare const F012_CHECKPOINT_VERSION = 7;
|
|
870
912
|
/** The highest {@link Checkpoint.version} `runner.resume` will act on; a higher one is rejected pre-CAS with
|
|
871
913
|
* {@link CheckpointError} `unsupported_version` (the checkpoint stays `pending`, retryable on a newer worker).
|
|
872
|
-
* Raised to
|
|
873
|
-
* (
|
|
874
|
-
*
|
|
875
|
-
* (
|
|
876
|
-
export declare const MAX_SUPPORTED_CHECKPOINT_VERSION =
|
|
914
|
+
* Raised to 7 for F-012-bearing checkpoints — this worker reads v1 (legacy human), v2 (resource), v3
|
|
915
|
+
* (binding human/irreversible_ask), v4 (a pre-164 row, refused only when it carries the retired
|
|
916
|
+
* wall-clock allocation), v5 (token-allocation-bearing resource/approval), v6 (org-admission freeze
|
|
917
|
+
* state) and v7 (F-012 constraint-chain / delegation-provenance enforcement state). */
|
|
918
|
+
export declare const MAX_SUPPORTED_CHECKPOINT_VERSION = 7;
|
|
877
919
|
/**
|
|
878
920
|
* Read a checkpoint's schema version, defaulting an absent field to **legacy `0`** (a 1.67-era checkpoint
|
|
879
921
|
* written before the field existed — it carries no `workspaceHandle`, so resuming it the v1 way is safe).
|
|
@@ -992,8 +1034,16 @@ export interface ResolvedOutcome {
|
|
|
992
1034
|
* RETRY of the ALREADY-APPROVED action — the re-resume MUST replay the persisted {@link ResolvedOutcome}
|
|
993
1035
|
* winner, never a new vote — while a `tool_unavailable` reopen (the action could not run, may now be
|
|
994
1036
|
* invalid — P-7) lets a human RE-DECIDE with the tool present, so a fresh operator decision IS allowed.
|
|
1037
|
+
*
|
|
1038
|
+
* `constraint_rejected` (F-012 L3) carries the same FRESH-REDECISION semantics as `tool_unavailable`:
|
|
1039
|
+
* a resolve was retracted because its decided INPUT failed a constraint re-adjudication (a frozen
|
|
1040
|
+
* ancestor projection / resume-edit policy denied the approver's edit) — the ask itself is unconsumed,
|
|
1041
|
+
* so the returned-to-pending row takes a fresh operator decision, never a forced replay of the
|
|
1042
|
+
* rejected one. Core's own edit re-adjudication runs PRE-CAS (nothing to reopen there); this reason
|
|
1043
|
+
* exists for a deployment lane that discovers the same rejection only after its own resolve landed
|
|
1044
|
+
* and must return the row to pending without inventing an out-of-vocabulary reason.
|
|
995
1045
|
*/
|
|
996
|
-
export type ReopenReason = "env_failed" | "tool_unavailable";
|
|
1046
|
+
export type ReopenReason = "env_failed" | "tool_unavailable" | "constraint_rejected";
|
|
997
1047
|
/**
|
|
998
1048
|
* design/80 D-1 (atomicity fix): the optimistic-concurrency precondition a {@link CheckpointStore.resolve}
|
|
999
1049
|
* caller passes so its CAS is atomic with the validation it did against an earlier `get()` snapshot. The
|
|
@@ -1361,10 +1411,23 @@ export declare class CheckpointError extends Error {
|
|
|
1361
1411
|
| "resume.parent_constraint_missing"
|
|
1362
1412
|
/** Parent effective-policy inheritance (shape check): the resume DID re-supply
|
|
1363
1413
|
* `internals.inheritedGate.parentConstraints`, but its length differs from the checkpoint's recorded
|
|
1364
|
-
* `parentConstraintCount` —
|
|
1414
|
+
* `parentConstraintCount` — or (F-012 L1) its projection CONTENT digests differently from the
|
|
1415
|
+
* recorded `constraintDigest` — a partial/mismatched re-supply would silently run the child under a
|
|
1365
1416
|
* thinner (or different) ancestor chain than it suspended with. Rejected pre-CAS (the checkpoint
|
|
1366
1417
|
* stays `pending`); re-resume with the full original chain. */
|
|
1367
|
-
| "resume.parent_constraint_mismatch"
|
|
1418
|
+
| "resume.parent_constraint_mismatch"
|
|
1419
|
+
/** F-012 L3: the resume carried an approver EDIT (`updatedInput`) and a FROZEN inherited ancestor
|
|
1420
|
+
* constraint (the persisted projection chain) — or the deployment's resume-edit policy — DENIES the
|
|
1421
|
+
* edited args. Refused PRE-CAS with fresh-redecision semantics: the checkpoint stays `pending` and
|
|
1422
|
+
* the same token remains decidable (approve as shown, deny, or edit differently) — the rejection
|
|
1423
|
+
* consumed the decision's INPUT, never the ask itself. */
|
|
1424
|
+
| "resume.constraint_rejected"
|
|
1425
|
+
/** F-012 L3: the resume carried an approver EDIT but the inherited ancestor chain cannot be
|
|
1426
|
+
* re-adjudicated statically — an OPAQUE layer is present, a layer declares a live remainder beyond
|
|
1427
|
+
* its projection, or the checkpoint predates the projection chain. "Would have been allowed" is not
|
|
1428
|
+
* computable from a self-reported closure, so the edited resume is refused typed pre-CAS (the
|
|
1429
|
+
* checkpoint stays `pending`, still decidable); an UNEDITED approval is untouched. */
|
|
1430
|
+
| "resume.constraint_unprojectable";
|
|
1368
1431
|
/** Structured discrimination a caller can branch on where the code alone is ambiguous. Today one
|
|
1369
1432
|
* member: `field` names WHICH part of the decision payload was rejected on a
|
|
1370
1433
|
* `checkpoint.invalid_outcome` — `"boundCallId"` (the action you decided on has been replaced:
|
|
@@ -1457,10 +1520,23 @@ export declare class CheckpointError extends Error {
|
|
|
1457
1520
|
| "resume.parent_constraint_missing"
|
|
1458
1521
|
/** Parent effective-policy inheritance (shape check): the resume DID re-supply
|
|
1459
1522
|
* `internals.inheritedGate.parentConstraints`, but its length differs from the checkpoint's recorded
|
|
1460
|
-
* `parentConstraintCount` —
|
|
1523
|
+
* `parentConstraintCount` — or (F-012 L1) its projection CONTENT digests differently from the
|
|
1524
|
+
* recorded `constraintDigest` — a partial/mismatched re-supply would silently run the child under a
|
|
1461
1525
|
* thinner (or different) ancestor chain than it suspended with. Rejected pre-CAS (the checkpoint
|
|
1462
1526
|
* stays `pending`); re-resume with the full original chain. */
|
|
1463
|
-
| "resume.parent_constraint_mismatch"
|
|
1527
|
+
| "resume.parent_constraint_mismatch"
|
|
1528
|
+
/** F-012 L3: the resume carried an approver EDIT (`updatedInput`) and a FROZEN inherited ancestor
|
|
1529
|
+
* constraint (the persisted projection chain) — or the deployment's resume-edit policy — DENIES the
|
|
1530
|
+
* edited args. Refused PRE-CAS with fresh-redecision semantics: the checkpoint stays `pending` and
|
|
1531
|
+
* the same token remains decidable (approve as shown, deny, or edit differently) — the rejection
|
|
1532
|
+
* consumed the decision's INPUT, never the ask itself. */
|
|
1533
|
+
| "resume.constraint_rejected"
|
|
1534
|
+
/** F-012 L3: the resume carried an approver EDIT but the inherited ancestor chain cannot be
|
|
1535
|
+
* re-adjudicated statically — an OPAQUE layer is present, a layer declares a live remainder beyond
|
|
1536
|
+
* its projection, or the checkpoint predates the projection chain. "Would have been allowed" is not
|
|
1537
|
+
* computable from a self-reported closure, so the edited resume is refused typed pre-CAS (the
|
|
1538
|
+
* checkpoint stays `pending`, still decidable); an UNEDITED approval is untouched. */
|
|
1539
|
+
| "resume.constraint_unprojectable", message: string,
|
|
1464
1540
|
/** Structured discrimination a caller can branch on where the code alone is ambiguous. Today one
|
|
1465
1541
|
* member: `field` names WHICH part of the decision payload was rejected on a
|
|
1466
1542
|
* `checkpoint.invalid_outcome` — `"boundCallId"` (the action you decided on has been replaced:
|
|
@@ -1633,6 +1709,19 @@ export interface CheckpointStore {
|
|
|
1633
1709
|
* that still matches.
|
|
1634
1710
|
*/
|
|
1635
1711
|
readonly fidelity?: StoreFidelity;
|
|
1712
|
+
/**
|
|
1713
|
+
* F-012 L2/L3 — the RE-DECISION capability declaration: whether this store's `reopen` (and, when
|
|
1714
|
+
* present, a durable validating-lease discipline) actually works — a promise, not a method-presence
|
|
1715
|
+
* sniff (`reopen` is optional and a stub that returns `false` still satisfies the type). The
|
|
1716
|
+
* sandbox-admission mode's pre-flight REQUIRES `redecision.reopen === true` (its boundary asks park
|
|
1717
|
+
* to a human and must be re-decidable); absent reads fail-closed as "no declared re-decision face"
|
|
1718
|
+
* and the mode refuses to arm, loudly. Same "declaration, never duck-typing" posture as
|
|
1719
|
+
* {@link retention}/{@link durability}/{@link fidelity}.
|
|
1720
|
+
*/
|
|
1721
|
+
readonly redecision?: {
|
|
1722
|
+
reopen: boolean;
|
|
1723
|
+
validatingLease?: boolean;
|
|
1724
|
+
};
|
|
1636
1725
|
/** Create-once. Throws {@link CheckpointError} `already_exists` on a token collision (never a silent
|
|
1637
1726
|
* overwrite — a reused token would clobber a live suspension). */
|
|
1638
1727
|
put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
|
|
@@ -1887,6 +1976,10 @@ export declare class InMemoryCheckpointStore implements CheckpointStore {
|
|
|
1887
1976
|
/** Honest declaration on the fidelity axis: `put`/`get` are `structuredClone`, so a row keeps the
|
|
1888
1977
|
* whole structured-clone domain — a `Date` filed here is a `Date` when it comes back. */
|
|
1889
1978
|
readonly fidelity: "structured-clone";
|
|
1979
|
+
/** F-012 L2: this store's `reopen` is a real resolved→pending CAS (below) — declared, not sniffed. */
|
|
1980
|
+
readonly redecision: {
|
|
1981
|
+
readonly reopen: true;
|
|
1982
|
+
};
|
|
1890
1983
|
private cps;
|
|
1891
1984
|
private fault;
|
|
1892
1985
|
put(token: CheckpointToken, cp: Checkpoint): Promise<void>;
|
|
@@ -133,7 +133,8 @@ export const RESOURCE_CHECKPOINT_VERSION = 2;
|
|
|
133
133
|
export const BINDING_CHECKPOINT_VERSION = 3;
|
|
134
134
|
export const TOKEN_CHECKPOINT_VERSION = 5;
|
|
135
135
|
export const ORG_ADMISSION_CHECKPOINT_VERSION = 6;
|
|
136
|
-
export const
|
|
136
|
+
export const F012_CHECKPOINT_VERSION = 7;
|
|
137
|
+
export const MAX_SUPPORTED_CHECKPOINT_VERSION = 7;
|
|
137
138
|
export function checkpointVersionOf(cp) {
|
|
138
139
|
return cp.version ?? 0;
|
|
139
140
|
}
|
|
@@ -458,6 +459,7 @@ export class InMemoryCheckpointStore {
|
|
|
458
459
|
retention = "none";
|
|
459
460
|
durability = "process-local";
|
|
460
461
|
fidelity = "structured-clone";
|
|
462
|
+
redecision = { reopen: true };
|
|
461
463
|
cps = new Map();
|
|
462
464
|
fault = null;
|
|
463
465
|
async put(token, cp) {
|
|
@@ -29,3 +29,41 @@ export declare const NON_GOVERNANCE_MEMORY_CODES: ReadonlySet<string>;
|
|
|
29
29
|
* `undefined` (NOT governance — the caller falls through to its ordinary taxonomy).
|
|
30
30
|
*/
|
|
31
31
|
export declare function governanceRetryClass(code: string): GovernanceRetryClass | undefined;
|
|
32
|
+
/**
|
|
33
|
+
* The CLOSED registry of reasons a permission-rule sync round may refuse an inbound record or move a
|
|
34
|
+
* local row into the quarantine area (design/182 §4.3/§5.2/§8.1). Same doctrine as
|
|
35
|
+
* {@link GOVERNANCE_CODES}: the machine reads THIS table, never free text — "silently dropped" is a
|
|
36
|
+
* contract violation, so every drop names a registered code and a consumer can branch on it.
|
|
37
|
+
*
|
|
38
|
+
* These are DISCLOSURE codes, not subagent error codes: they never enter {@link GOVERNANCE_CODES}
|
|
39
|
+
* (that table classifies a failed child's error for retry, and its gate enforces a suffix convention
|
|
40
|
+
* these codes do not carry). The value states which side of the wire loses bytes:
|
|
41
|
+
* `inbound-refused` = the peer's copy was refused here (the peer still holds it — no loss);
|
|
42
|
+
* `local-quarantined` = a LOCAL row left the live view into the quarantine area (bytes preserved,
|
|
43
|
+
* disclosed, re-enterable only through a new consent).
|
|
44
|
+
*/
|
|
45
|
+
export declare const RULE_SYNC_DROP_CODES: {
|
|
46
|
+
/** The single validator refused the rule text (e.g. a bare-interpreter prefix). Both directions. */
|
|
47
|
+
readonly invalid_rule_text: "inbound-refused-or-local-quarantined";
|
|
48
|
+
/** Stored tool/match/command disagree with the re-projection of the rule text. Both directions. */
|
|
49
|
+
readonly metadata_mismatch: "inbound-refused-or-local-quarantined";
|
|
50
|
+
/** An inbound dot claims THIS replica's actor above its minted counter — forgery on our own name.
|
|
51
|
+
* Covers adds, a tombstone's `deletedBy`, AND a tombstone's `removedDots` (a tombstone naming our
|
|
52
|
+
* future dot would pre-position a kill for an approval not yet given). */
|
|
53
|
+
readonly own_actor_forged: "inbound-refused";
|
|
54
|
+
/** An inbound add reuses a dot that already identifies a DIFFERENT (rule, scope) row — a dot is
|
|
55
|
+
* minted once for one add, so a reuse is damage or forgery, and admitting it would let quarantine
|
|
56
|
+
* or tombstone identity on one row reach the other. */
|
|
57
|
+
readonly dot_identity_conflict: "inbound-refused";
|
|
58
|
+
/** A live local add at-or-below the collected GC frontier and absent from the inbound state — the
|
|
59
|
+
* residue of an already-recycled tombstone. Never resurrected; quarantined and disclosed. */
|
|
60
|
+
readonly below_gc_frontier: "local-quarantined";
|
|
61
|
+
/** The server refused this local row (its response `dropped` names it); it leaves the live view so
|
|
62
|
+
* it does not ride — and get refused on — every future round. */
|
|
63
|
+
readonly server_rejected: "local-quarantined";
|
|
64
|
+
};
|
|
65
|
+
/** Every reason a sync round may drop or quarantine a record. Closed set; free text is not a member. */
|
|
66
|
+
export type RuleSyncDropReason = keyof typeof RULE_SYNC_DROP_CODES;
|
|
67
|
+
/** The subset that may appear on a LOCAL quarantined row (design/182 §8.1 `quarantine` instruction /
|
|
68
|
+
* fence arm / local screening). `own_actor_forged` is inbound-only by construction. */
|
|
69
|
+
export type RuleQuarantineReason = Exclude<RuleSyncDropReason, "own_actor_forged">;
|
|
@@ -14,6 +14,9 @@ export const NON_GOVERNANCE_MEMORY_CODES = new Set([
|
|
|
14
14
|
"memory.partition_split",
|
|
15
15
|
"memory.tail",
|
|
16
16
|
"memory.pollution_mark_failed",
|
|
17
|
+
"memory.challenge_sweep_failed",
|
|
18
|
+
"memory.lineage_settle_failed",
|
|
19
|
+
"memory.challenge_ledger_corrupt",
|
|
17
20
|
]);
|
|
18
21
|
export function governanceRetryClass(code) {
|
|
19
22
|
if (Object.prototype.hasOwnProperty.call(GOVERNANCE_CODES, code)) {
|
|
@@ -23,3 +26,11 @@ export function governanceRetryClass(code) {
|
|
|
23
26
|
return "terminal";
|
|
24
27
|
return undefined;
|
|
25
28
|
}
|
|
29
|
+
export const RULE_SYNC_DROP_CODES = {
|
|
30
|
+
invalid_rule_text: "inbound-refused-or-local-quarantined",
|
|
31
|
+
metadata_mismatch: "inbound-refused-or-local-quarantined",
|
|
32
|
+
own_actor_forged: "inbound-refused",
|
|
33
|
+
dot_identity_conflict: "inbound-refused",
|
|
34
|
+
below_gc_frontier: "local-quarantined",
|
|
35
|
+
server_rejected: "local-quarantined",
|
|
36
|
+
};
|
package/dist/core/hooks.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { DocumentContent, ImageContent, TextContent } from "../internal/llm.js";
|
|
2
2
|
import type { ExecutionEnv, FileError, Result, SessionTreeEntry } from "../internal/harness-types.js";
|
|
3
3
|
import type { PermissionResult, ResolvedAsk, ToolCallRequest, ToolPolicy } from "./tool-policy.js";
|
|
4
|
+
import { type AskClass } from "./ask-class.js";
|
|
4
5
|
/**
|
|
5
6
|
* In-process hook seam (design/37) — a small, provider-agnostic interception layer modeled on CC's
|
|
6
7
|
* hooks but reduced to three process-internal callbacks (no shell/HTTP executors, no settings files):
|
|
@@ -712,6 +713,44 @@ export interface ToolGateInput {
|
|
|
712
713
|
rule: string;
|
|
713
714
|
}) => void;
|
|
714
715
|
};
|
|
716
|
+
/**
|
|
717
|
+
* F-012 L2 — the SANDBOX ADMISSION leg. Present ONLY when the runner armed the mode: the execution
|
|
718
|
+
* env DECLARES isolation (`RemoteExecutionEnv.capabilities.isolation === true` — a Runner-resolved
|
|
719
|
+
* fact, never caller self-report) AND the park prerequisites hold (durable park configured + a
|
|
720
|
+
* checkpoint store that explicitly declares `redecision.reopen`). Absent ⇒ the whole step does not
|
|
721
|
+
* exist and the decision path is byte-identical to a build without it.
|
|
722
|
+
*
|
|
723
|
+
* Semantics (clay's ruling): inside a declared sandbox, an engine-authored "confirm a potentially
|
|
724
|
+
* destructive local action" ask is answered by containment itself — auto-allow + durable
|
|
725
|
+
* disclosure (`permission.sandbox_admitted`), never a headless deny that pollutes the model.
|
|
726
|
+
* STRICT dominance governs: the leg admits only when EVERY surviving ask on the call is classified
|
|
727
|
+
* `sandbox_local` in the engine's private envelope (deployment-policy asks are `unknown`, hook
|
|
728
|
+
* asks `hook`, marked calls `mandate`, `requiresRealApproval` asks external — none admit), and
|
|
729
|
+
* the call crosses NO declared boundary (`boundaryCapable`: egress-marked tools and
|
|
730
|
+
* protocol-mounted (MCP/A2A) tools — a server's self-report is always boundary-capable). A
|
|
731
|
+
* boundary-crossing ask under the armed mode takes the ordinary durable-park leg below — "park to
|
|
732
|
+
* a human", never an auto-deny.
|
|
733
|
+
*/
|
|
734
|
+
sandboxAdmission?: {
|
|
735
|
+
/** The ask classes recorded for this call's fold-layer asks (with their source layers), or
|
|
736
|
+
* `undefined` when no fold layer asked — the gate's own safety tighten is then the only asker. */
|
|
737
|
+
askClassesOf: (toolCallId: string) => ReadonlyArray<{
|
|
738
|
+
cls: AskClass;
|
|
739
|
+
layer: string;
|
|
740
|
+
}> | undefined;
|
|
741
|
+
/** This call's tool crosses a DECLARED boundary (egress axis / protocol-mounted) — never admitted. */
|
|
742
|
+
boundaryCapable: boolean;
|
|
743
|
+
/** The durable disclosure sink — fired on every admission BEFORE the allow is returned. */
|
|
744
|
+
onAdmitted: (info: {
|
|
745
|
+
toolCallId: string;
|
|
746
|
+
toolName: string;
|
|
747
|
+
askClasses: ReadonlyArray<{
|
|
748
|
+
cls: AskClass;
|
|
749
|
+
layer: string;
|
|
750
|
+
}>;
|
|
751
|
+
boundary: "sandbox_internal";
|
|
752
|
+
}) => void;
|
|
753
|
+
};
|
|
715
754
|
}
|
|
716
755
|
/**
|
|
717
756
|
* Fold a {@link Hooks.preToolUse} callback into a {@link ToolPolicy} layer, so a screening face a task
|
package/dist/core/hooks.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { decisionText, refuseOutOfContractDecision } from "./tool-policy.js";
|
|
2
|
+
import { brandPolicyAskClass } from "./ask-class.js";
|
|
2
3
|
import { inlineUntrusted } from "./untrusted-text.js";
|
|
3
4
|
import { ASK_USER_QUESTION_TOOL_NAME } from "./ask-question.js";
|
|
4
5
|
import { createSafeNotifier } from "./safe-notify.js";
|
|
@@ -99,7 +100,7 @@ function screenPreToolUseResult(r) {
|
|
|
99
100
|
return refuseOutOfContractDecision(r);
|
|
100
101
|
}
|
|
101
102
|
export function createPreToolUseConstraintPolicy(preToolUse, env, onCrash) {
|
|
102
|
-
return {
|
|
103
|
+
return brandPolicyAskClass({
|
|
103
104
|
check: async (req) => {
|
|
104
105
|
let r;
|
|
105
106
|
try {
|
|
@@ -128,7 +129,7 @@ export function createPreToolUseConstraintPolicy(preToolUse, env, onCrash) {
|
|
|
128
129
|
}
|
|
129
130
|
return r.updatedInput !== undefined ? { action: "allow", updatedInput: r.updatedInput } : { action: "allow" };
|
|
130
131
|
},
|
|
131
|
-
};
|
|
132
|
+
}, "hook");
|
|
132
133
|
}
|
|
133
134
|
function traceHookCrash(input, err, notifier) {
|
|
134
135
|
notifier.notify(() => input.onHookError?.(err), "toolGate.onHookError");
|
|
@@ -293,6 +294,29 @@ export async function runToolGate(input) {
|
|
|
293
294
|
denySource = "classifier";
|
|
294
295
|
}
|
|
295
296
|
}
|
|
297
|
+
if (input.sandboxAdmission !== undefined &&
|
|
298
|
+
decision.action === "ask" &&
|
|
299
|
+
req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
|
|
300
|
+
input.isMarkedUnresolvable?.(input.event.toolCallId) !== true &&
|
|
301
|
+
decision.requiresRealApproval !== true &&
|
|
302
|
+
decision.decisionReason !== "hook" &&
|
|
303
|
+
hookAsk === undefined &&
|
|
304
|
+
input.egress !== true &&
|
|
305
|
+
input.sandboxAdmission.boundaryCapable !== true) {
|
|
306
|
+
const recorded = input.sandboxAdmission.askClassesOf(toolCallId);
|
|
307
|
+
const admissible = recorded !== undefined && recorded.length > 0 && recorded.every((r) => r.cls === "sandbox_local");
|
|
308
|
+
if (admissible) {
|
|
309
|
+
const askClasses = recorded;
|
|
310
|
+
input.sandboxAdmission.onAdmitted({ toolCallId, toolName, askClasses, boundary: "sandbox_internal" });
|
|
311
|
+
preToolContext.push(`permission.sandbox_admitted: this call's pending approval was auto-admitted because the deployment declares an isolated sandbox execution env and the request stays inside it. Recorded for audit.`);
|
|
312
|
+
decision = {
|
|
313
|
+
action: "allow",
|
|
314
|
+
message: "admitted inside the deployment-declared sandbox (permission.sandbox_admitted)",
|
|
315
|
+
decisionReason: "sandbox",
|
|
316
|
+
...(policyRewrite !== undefined ? { updatedInput: policyRewrite } : {}),
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
}
|
|
296
320
|
const egressTool = input.egress === true;
|
|
297
321
|
const irreversibleTool = input.irreversibility === "always" || input.irreversibility === "maybe";
|
|
298
322
|
const safety = egressTool || irreversibleTool ? { egress: egressTool, irreversible: irreversibleTool } : undefined;
|
|
@@ -18,7 +18,7 @@ export declare const LOCKED_KEY_REGISTRY: {
|
|
|
18
18
|
readonly specFields: readonly ["mcp"];
|
|
19
19
|
};
|
|
20
20
|
readonly toolPolicy: {
|
|
21
|
-
readonly specFields: readonly ["toolPolicy"];
|
|
21
|
+
readonly specFields: readonly ["toolPolicy", "basePolicyForResumeEdit"];
|
|
22
22
|
};
|
|
23
23
|
readonly compliancePosture: {
|
|
24
24
|
readonly specFields: readonly [];
|
|
@@ -46,6 +46,12 @@ export interface LockedPreflight {
|
|
|
46
46
|
* always enforced, resolved ONCE here. With `toolPolicy` locked, the deployment's own
|
|
47
47
|
* `deps.toolPolicy` is the whole answer (a spec-supplied policy has already been refused). */
|
|
48
48
|
toolPolicy: ToolPolicy | undefined;
|
|
49
|
+
/** F-012 (codex round 8): the effective resume-edit re-adjudication OVERRIDE, resolved lock-aware in
|
|
50
|
+
* the SAME slot as {@link toolPolicy}. With `toolPolicy` locked, a spec-supplied
|
|
51
|
+
* `basePolicyForResumeEdit` has already been refused wholesale, so this is the deployment's own
|
|
52
|
+
* `deps.basePolicyForResumeEdit` (or undefined — the caller-policy fallback then applies at the use
|
|
53
|
+
* site). Unlocked: `spec.basePolicyForResumeEdit ?? deps.basePolicyForResumeEdit`. */
|
|
54
|
+
basePolicyForResumeEdit: ToolPolicy | undefined;
|
|
49
55
|
}
|
|
50
56
|
/**
|
|
51
57
|
* Validate a deployment's lock declaration against the closed registry. Fail-loud on any unknown
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const LOCKED_KEY_REGISTRY = {
|
|
2
2
|
mcp: { specFields: ["mcp"] },
|
|
3
|
-
toolPolicy: { specFields: ["toolPolicy"] },
|
|
3
|
+
toolPolicy: { specFields: ["toolPolicy", "basePolicyForResumeEdit"] },
|
|
4
4
|
compliancePosture: { specFields: [] },
|
|
5
5
|
retentionPolicy: { specFields: [] },
|
|
6
6
|
};
|
|
@@ -38,5 +38,6 @@ export function preflightLockedConfig(spec, deps) {
|
|
|
38
38
|
lockedKeys,
|
|
39
39
|
mcp: lockedKeys.has("mcp") ? undefined : spec.mcp,
|
|
40
40
|
toolPolicy: lockedKeys.has("toolPolicy") ? deps.toolPolicy : (spec.toolPolicy ?? deps.toolPolicy),
|
|
41
|
+
basePolicyForResumeEdit: lockedKeys.has("toolPolicy") ? deps.basePolicyForResumeEdit : (spec.basePolicyForResumeEdit ?? deps.basePolicyForResumeEdit),
|
|
41
42
|
};
|
|
42
43
|
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design/180 half A — delegation RUNTIME provenance: the three-value attestation and its monotonic
|
|
3
|
+
* aggregate. The static tool-face judgment ({@link import("./content-origin.js").delegationCallIsExternal})
|
|
4
|
+
* stays the PERMANENT FLOOR: runtime evidence may only retire the static verdict when a COMPLETE
|
|
5
|
+
* clean proof is in hand; any evidence gap falls back to the floor. "We could not prove it clean"
|
|
6
|
+
* and "it is clean" are different answers — replacing the floor outright would fail OPEN on every
|
|
7
|
+
* evidence gap (old child vintages, crashes, un-armed sessions).
|
|
8
|
+
*
|
|
9
|
+
* The aggregate is two MONOTONIC bits plus versioning:
|
|
10
|
+
* - `sawExternal` — an actually-observed polluting-class invocation (own recorder, or a delivered
|
|
11
|
+
* child attestation of `external`). Once set, never cleared.
|
|
12
|
+
* - `incomplete` — some stretch of the session's conduct is unprovable (a delegation delivered no /
|
|
13
|
+
* an unknown attestation, a resume lost the aggregate, the recorder was not armed from turn 0).
|
|
14
|
+
* Once set, never cleared.
|
|
15
|
+
* Reduction order is FIXED (design/180 r3): `sawExternal ⇒ external` > `incomplete ⇒ unknown` >
|
|
16
|
+
* `clean` — an observed external event always outranks an evidence gap (missing evidence can make
|
|
17
|
+
* "clean" unprovable; it can never make an observed event disappear).
|
|
18
|
+
*/
|
|
19
|
+
/** The three-value delegation attestation. Only `clean` changes behavior (the parent skips the
|
|
20
|
+
* static mark); `unknown` is byte-equivalent to absence (static floor governs); `external` marks
|
|
21
|
+
* unconditionally, with the observed-event reason. */
|
|
22
|
+
export type DelegationAttestation = "clean" | "external" | "unknown";
|
|
23
|
+
/** The monotonic per-run provenance aggregate (in-process this slice; the durable-checkpoint
|
|
24
|
+
* persistence leg rides the background-face slice — absence after a resume reads `incomplete`,
|
|
25
|
+
* which is the fail-closed default this shape is built around). */
|
|
26
|
+
export interface DelegationProvenanceAggregate {
|
|
27
|
+
version: 1;
|
|
28
|
+
/** Turn index the recorder was armed from. 0 ⇔ armed at prepare (full coverage). */
|
|
29
|
+
coverageStartTurn: number;
|
|
30
|
+
sawExternal: boolean;
|
|
31
|
+
incomplete: boolean;
|
|
32
|
+
}
|
|
33
|
+
/** The frozen content-safety snapshot a parent hands its delegated children (design/180 A-2): the
|
|
34
|
+
* classification config the child's recorder must judge under. Inherit-narrow-only — see
|
|
35
|
+
* {@link narrowContentSafety}. */
|
|
36
|
+
export interface DelegationContentSafety {
|
|
37
|
+
trustedTools: readonly string[];
|
|
38
|
+
execIsExternalContent: boolean;
|
|
39
|
+
}
|
|
40
|
+
export declare function newDelegationProvenanceAggregate(): DelegationProvenanceAggregate;
|
|
41
|
+
/**
|
|
42
|
+
* Effective content-safety for a run: the parent chain's frozen snapshot ∩ the run's own config.
|
|
43
|
+
* A child may NARROW what the chain trusts (fewer trusted tools, stricter exec), never widen it —
|
|
44
|
+
* a wider child-side allowlist must not launder a chain-external tool into `local` on the way up.
|
|
45
|
+
* At least one input must be present (the caller only computes this when armed).
|
|
46
|
+
*/
|
|
47
|
+
export declare function narrowContentSafety(parent: DelegationContentSafety | undefined, own: {
|
|
48
|
+
trustedTools: ReadonlySet<string>;
|
|
49
|
+
execIsExternalContent: boolean;
|
|
50
|
+
} | undefined): {
|
|
51
|
+
trustedTools: Set<string>;
|
|
52
|
+
execIsExternalContent: boolean;
|
|
53
|
+
};
|
|
54
|
+
/** Reduce an aggregate to the delivered attestation. `completed` = the child reached its terminal
|
|
55
|
+
* normally with its full result delivered — a salvage/partial/errored child can never prove clean
|
|
56
|
+
* (its conduct after the cut is unobserved), though an OBSERVED external event still reports. */
|
|
57
|
+
export declare function reduceDelegationAttestation(agg: DelegationProvenanceAggregate | undefined, opts: {
|
|
58
|
+
completed: boolean;
|
|
59
|
+
}): DelegationAttestation;
|
|
60
|
+
/** Read a delivered card's attestation field (the explicit-whitelist handback seat). Anything but
|
|
61
|
+
* the three spellings — including absence — reads as undefined, i.e. the static floor governs. */
|
|
62
|
+
export declare function readCardAttestation(details: unknown): DelegationAttestation | undefined;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function newDelegationProvenanceAggregate() {
|
|
2
|
+
return { version: 1, coverageStartTurn: 0, sawExternal: false, incomplete: false };
|
|
3
|
+
}
|
|
4
|
+
export function narrowContentSafety(parent, own) {
|
|
5
|
+
if (parent === undefined) {
|
|
6
|
+
return { trustedTools: new Set(own?.trustedTools ?? []), execIsExternalContent: own?.execIsExternalContent ?? false };
|
|
7
|
+
}
|
|
8
|
+
const fromParent = new Set(parent.trustedTools);
|
|
9
|
+
const trustedTools = own === undefined ? fromParent : new Set([...fromParent].filter((t) => own.trustedTools.has(t)));
|
|
10
|
+
return { trustedTools, execIsExternalContent: parent.execIsExternalContent || (own?.execIsExternalContent ?? false) };
|
|
11
|
+
}
|
|
12
|
+
export function reduceDelegationAttestation(agg, opts) {
|
|
13
|
+
if (agg === undefined)
|
|
14
|
+
return "unknown";
|
|
15
|
+
if (agg.sawExternal)
|
|
16
|
+
return "external";
|
|
17
|
+
if (agg.incomplete || !opts.completed || agg.version !== 1 || agg.coverageStartTurn !== 0)
|
|
18
|
+
return "unknown";
|
|
19
|
+
return "clean";
|
|
20
|
+
}
|
|
21
|
+
export function readCardAttestation(details) {
|
|
22
|
+
if (details === null || typeof details !== "object")
|
|
23
|
+
return undefined;
|
|
24
|
+
const v = details.attestation;
|
|
25
|
+
return v === "clean" || v === "external" || v === "unknown" ? v : undefined;
|
|
26
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type MemoryPartitionIncidentSink, type RetrievedAccountRow, type SessionPollutionRecord } from "./layout.js";
|
|
1
|
+
import { type ChallengeAssignment, type ChallengeEvent, type ChallengedHistoryRow, type LineagePendingTxn, type LineagePromotion, type MemoryPartitionIncidentSink, type RetrievedAccountRow, type SessionPollutionRecord } from "./layout.js";
|
|
2
2
|
import type { HarvestReport, MemoryAnnouncement, MemoryBackend, MemorySessionHandle, ScanFinding } from "./types.js";
|
|
3
3
|
/**
|
|
4
4
|
* The CC `# Memory` instruction section — VERBATIM from the live capture
|
|
@@ -31,6 +31,15 @@ export declare function buildMemoryInstruction(memoryDir: string, instructionFil
|
|
|
31
31
|
* dual-root instruction merge ("the write plane's instruction wins") never sees a second candidate.
|
|
32
32
|
*/
|
|
33
33
|
export declare const MEMORY_RECALL_DISCIPLINE = "Before answering questions about earlier work, decisions, dates, people, or the user's preferences, look them up: `memory_search` finds entries by keyword and `memory_get` reads a full entry \u2014 the injected memory index only lists what exists. When a lookup comes up empty, say that you checked memory and found nothing instead of guessing.";
|
|
34
|
+
/**
|
|
35
|
+
* design/180 B-3 — the preference-header discipline paragraph. Same carriage as
|
|
36
|
+
* {@link MEMORY_RECALL_DISCIPLINE}: a runner-appended segment OUTSIDE `inject()`, so the
|
|
37
|
+
* CC-verbatim `# Memory` instruction stays byte-identical to its sha256-pinned capture. Appended
|
|
38
|
+
* only when a WRITE channel exists (prepare-memory gates it) — the lines it teaches are frontmatter
|
|
39
|
+
* the MODEL writes; the engine has no write arm for them (writing usage evidence for the model
|
|
40
|
+
* would forge the very account the three-tier discipline forbids forging).
|
|
41
|
+
*/
|
|
42
|
+
export declare const MEMORY_PREFERENCE_DISCIPLINE = "When the user confirms a stored preference or fact still holds, refresh that entry's `last-confirmed: <YYYY-MM-DD>` frontmatter line (add it when absent). When you save a preference, add an `applies-when: <context>` frontmatter line naming when it applies. Both are plain frontmatter lines \u2014 write them yourself; nothing fills them in for you.";
|
|
34
43
|
/** CC index-injection parameters: MEMORY.md's first 200 lines / 25KB enter the prompt. */
|
|
35
44
|
export declare const MEMORY_INDEX_MAX_LINES = 200;
|
|
36
45
|
export declare const MEMORY_INDEX_MAX_BYTES: number;
|
|
@@ -188,6 +197,62 @@ export declare class MemoryEngine {
|
|
|
188
197
|
/** The session's pollution record (in-process mark first, then the durable marker) — undefined =
|
|
189
198
|
* clean. Reads are side-effect-free. */
|
|
190
199
|
sessionPollution(sessionId: string): SessionPollutionRecord | undefined;
|
|
200
|
+
/** Entry ids the session's memory tools RETURNED this process — the retrieved half of the
|
|
201
|
+
* challenged-history mechanical event (B-3). In-process on purpose: "same session" is one runner
|
|
202
|
+
* process by architecture, and the durable retrieved account is not session-keyed. */
|
|
203
|
+
private readonly retrievedThisSession;
|
|
204
|
+
/**
|
|
205
|
+
* The model-visible read faces' EXCLUSION view: entry id → why it is withheld.
|
|
206
|
+
* - `challenged` — an unresolved S-grade challenge generation exists (B-2');
|
|
207
|
+
* - `lineage_pending` — the dirty latch: a staged lineage row for this id has not been settled
|
|
208
|
+
* (credentialed-but-unpromoted, or the undecidable crash window) — "the account is not
|
|
209
|
+
* settled, the content does not go on the table" (r4).
|
|
210
|
+
* THROWS {@link import("./layout.js").ControlPlaneCorruptError} when either ledger's integrity is
|
|
211
|
+
* unknowable — consumers must refuse service loudly (corrupt ≠ clean), the same law the pollution
|
|
212
|
+
* marker already follows.
|
|
213
|
+
*/
|
|
214
|
+
readChallengeExclusions(): Map<string, {
|
|
215
|
+
code: "challenged" | "lineage_pending";
|
|
216
|
+
generation?: number;
|
|
217
|
+
at?: number;
|
|
218
|
+
}>;
|
|
219
|
+
/** The shared challenge writer: append events idempotently, then announce OPAQUELY (id + code +
|
|
220
|
+
* generation only — the announcement is a model-visible face, so no name/description/free reason
|
|
221
|
+
* rides it; content stays on the host audit face). Failures are disclosed, never thrown past. */
|
|
222
|
+
private challengeAndAnnounce;
|
|
223
|
+
/**
|
|
224
|
+
* Host API (advisory, control-plane trust level — the same plane that can mark pollution):
|
|
225
|
+
* challenge entries by id. `requestId` is REQUIRED (the generation ledger's idempotency identity;
|
|
226
|
+
* this engine never mints one for the caller — a retried request must carry the SAME id so a lost
|
|
227
|
+
* ack cannot open a second generation). Each entry's eventId derives as `<requestId>:<entryId>`,
|
|
228
|
+
* so one batched request stays per-entry attributable.
|
|
229
|
+
*/
|
|
230
|
+
challengeEntries(ids: readonly string[], reason: string, requestId: string): ChallengeAssignment[];
|
|
231
|
+
/** Host API: challenge every entry the lineage ledger attributes to `sessionId` (post-hoc source
|
|
232
|
+
* falsification — trustedTools misconfigured, a tool re-classified, late delegation evidence).
|
|
233
|
+
* Same requestId contract as {@link challengeEntries}. */
|
|
234
|
+
challengeSession(sessionId: string, reason: string, requestId: string): ChallengeAssignment[];
|
|
235
|
+
/** Host API: the TRUSTED resolve — closes exactly the named generation (a later challenge stays
|
|
236
|
+
* open; a resolve never blanket-clears, and there is no model-face path to this call). Returns
|
|
237
|
+
* false ⇔ no such generation was ever opened. */
|
|
238
|
+
resolveChallenge(entryId: string, generation: number, reason: string, requestId?: string): boolean;
|
|
239
|
+
/** Host API: adjudicate one UNDECIDABLE pending lineage transaction (the applyPatches-success →
|
|
240
|
+
* credential-write crash window; design/180 r5 — never auto-discarded). The host, holding its own
|
|
241
|
+
* deployment facts, states whether the commit landed. */
|
|
242
|
+
adjudicatePendingLineage(txnId: string, action: "promote" | "discard"): LineagePromotion[];
|
|
243
|
+
/** Host observability: the staged lineage transactions still awaiting settlement (their entry ids
|
|
244
|
+
* are latched — see {@link readChallengeExclusions}). Throws on a corrupt ledger. */
|
|
245
|
+
listPendingLineage(): Record<string, LineagePendingTxn>;
|
|
246
|
+
/** Host audit reads (throw on corruption — audit must not read a laundered ledger as clean). */
|
|
247
|
+
readChallengeLedger(): ChallengeEvent[];
|
|
248
|
+
/** Observability/tests — the collect-only challenged-history account (B-3; zero engine consumers). */
|
|
249
|
+
readChallengedHistoryAccount(): Record<string, ChallengedHistoryRow>;
|
|
250
|
+
/** Promotion settlement (shared by harvest step ⑤, reconciliation and host adjudication): a
|
|
251
|
+
* promoted contribution whose session ALREADY carries a durable pollution marker is challenged
|
|
252
|
+
* on the spot — "mark first, promote later" must converge exactly like the reverse order. The
|
|
253
|
+
* eventId anchors on the pollution record (sessionId + marked-at), so however many times this
|
|
254
|
+
* settlement replays the same pollution event, the generation is allocated once. */
|
|
255
|
+
private settlePromotions;
|
|
191
256
|
materialize(scopes: readonly string[], writeScope: string | null): Promise<MemorySessionHandle>;
|
|
192
257
|
inject(handle: MemorySessionHandle, opts?: {
|
|
193
258
|
writeToolMounted?: boolean;
|
|
@@ -222,6 +287,7 @@ export declare class MemoryEngine {
|
|
|
222
287
|
polluted?: {
|
|
223
288
|
reason: string;
|
|
224
289
|
};
|
|
290
|
+
sessionId?: string;
|
|
225
291
|
}): Promise<HarvestReport>;
|
|
226
292
|
private harvestCore;
|
|
227
293
|
/**
|