@sema-agent/core 5.21.1 → 5.23.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.
Files changed (96) hide show
  1. package/CHANGELOG.md +103 -0
  2. package/dist/agents/send-message-tool.js +6 -3
  3. package/dist/agents/subagent.d.ts +6 -0
  4. package/dist/agents/subagent.js +48 -6
  5. package/dist/brain/errors.d.ts +20 -0
  6. package/dist/brain/errors.js +40 -0
  7. package/dist/brain/retry.d.ts +16 -2
  8. package/dist/brain/retry.js +3 -2
  9. package/dist/brain/status-sink.d.ts +9 -2
  10. package/dist/brain/stream-engine.d.ts +22 -0
  11. package/dist/brain/stream-engine.js +41 -10
  12. package/dist/core/ask-class.d.ts +48 -0
  13. package/dist/core/ask-class.js +33 -0
  14. package/dist/core/checkpoint-store.d.ts +103 -10
  15. package/dist/core/checkpoint-store.js +3 -1
  16. package/dist/core/governance-codes.d.ts +38 -0
  17. package/dist/core/governance-codes.js +14 -0
  18. package/dist/core/hooks.d.ts +101 -1
  19. package/dist/core/hooks.js +116 -14
  20. package/dist/core/locked-config.d.ts +7 -1
  21. package/dist/core/locked-config.js +2 -1
  22. package/dist/core/memory-engine/delegation-provenance.d.ts +62 -0
  23. package/dist/core/memory-engine/delegation-provenance.js +26 -0
  24. package/dist/core/memory-engine/engine.d.ts +94 -1
  25. package/dist/core/memory-engine/engine.js +329 -12
  26. package/dist/core/memory-engine/header-hints.d.ts +30 -0
  27. package/dist/core/memory-engine/header-hints.js +41 -0
  28. package/dist/core/memory-engine/index.d.ts +3 -2
  29. package/dist/core/memory-engine/index.js +3 -2
  30. package/dist/core/memory-engine/layout.d.ts +232 -0
  31. package/dist/core/memory-engine/layout.js +469 -1
  32. package/dist/core/memory-engine/tools.d.ts +30 -0
  33. package/dist/core/memory-engine/tools.js +108 -17
  34. package/dist/core/permission-rule-consent.d.ts +25 -9
  35. package/dist/core/permission-rule-consent.js +93 -21
  36. package/dist/core/permission-rule-model.d.ts +9 -1
  37. package/dist/core/permission-rule-model.js +2 -2
  38. package/dist/core/permission-rule-org.d.ts +195 -0
  39. package/dist/core/permission-rule-org.js +234 -0
  40. package/dist/core/permission-rule-store.d.ts +249 -6
  41. package/dist/core/permission-rule-store.js +314 -3
  42. package/dist/core/permission-rule-sync.d.ts +139 -0
  43. package/dist/core/permission-rule-sync.js +343 -0
  44. package/dist/core/runner/prepare-memory.js +35 -8
  45. package/dist/core/runner/prepare-task.d.ts +64 -3
  46. package/dist/core/runner/prepare-task.js +357 -32
  47. package/dist/core/runner/runtask.js +166 -6
  48. package/dist/core/shared-memory/contract.js +19 -4
  49. package/dist/core/shared-memory/normalize.d.ts +3 -1
  50. package/dist/core/shared-memory/tools.js +73 -17
  51. package/dist/core/shared-memory/types.d.ts +27 -1
  52. package/dist/core/store-contracts/permission-rule-sync-contract.d.ts +33 -0
  53. package/dist/core/store-contracts/permission-rule-sync-contract.js +186 -0
  54. package/dist/core/task-notification.d.ts +5 -2
  55. package/dist/core/task-registry-agent.d.ts +1 -1
  56. package/dist/core/task-registry-agent.js +6 -2
  57. package/dist/core/task-registry-shared.d.ts +9 -2
  58. package/dist/core/task-registry.d.ts +9 -3
  59. package/dist/core/task-registry.js +2 -0
  60. package/dist/core/tool-policy.d.ts +155 -4
  61. package/dist/core/tool-policy.js +148 -10
  62. package/dist/core/tool-result-store.d.ts +9 -1
  63. package/dist/core/tool-result-store.js +2 -1
  64. package/dist/core/trace.d.ts +79 -1
  65. package/dist/core/types.d.ts +94 -3
  66. package/dist/core/wiring-manifest.d.ts +16 -1
  67. package/dist/core/wiring-manifest.js +7 -1
  68. package/dist/index.d.ts +15 -8
  69. package/dist/index.js +12 -5
  70. package/dist/orchestration/goal.d.ts +10 -0
  71. package/dist/orchestration/goal.js +6 -5
  72. package/dist/stores/file/adoption/adopt.d.ts +146 -0
  73. package/dist/stores/file/adoption/adopt.js +616 -0
  74. package/dist/stores/file/adoption/marker.d.ts +194 -0
  75. package/dist/stores/file/adoption/marker.js +198 -0
  76. package/dist/stores/file/background-agent-store.js +2 -0
  77. package/dist/stores/file/checkpoint-store.d.ts +4 -0
  78. package/dist/stores/file/checkpoint-store.js +3 -0
  79. package/dist/stores/file/file-snapshot-store.js +2 -0
  80. package/dist/stores/file/index.d.ts +2 -0
  81. package/dist/stores/file/index.js +4 -0
  82. package/dist/stores/file/mailbox-store.js +2 -0
  83. package/dist/stores/file/memory-store.js +2 -0
  84. package/dist/stores/file/permission-rule-adopt.d.ts +62 -0
  85. package/dist/stores/file/permission-rule-adopt.js +95 -0
  86. package/dist/stores/file/permission-rule-store.d.ts +80 -2
  87. package/dist/stores/file/permission-rule-store.js +189 -46
  88. package/dist/stores/file/session-policy-store.js +2 -0
  89. package/dist/stores/file/session-store.js +2 -0
  90. package/dist/stores/file/task-list-store.js +2 -0
  91. package/dist/stores/file/tool-result-store.js +2 -0
  92. package/dist/stores/file/usage-window-store.js +2 -0
  93. package/dist/stores/file/workflow-journal-store.js +2 -0
  94. package/dist/stores/file/workflow-run-store.js +2 -0
  95. package/dist/tools/fs/bash-readonly-classifier.js +59 -10
  96. package/package.json +3 -2
@@ -0,0 +1,48 @@
1
+ /**
2
+ * F-012 L2 — the ENGINE-INTERNAL ask-class envelope.
3
+ *
4
+ * An `ask` verdict's SEMANTIC CLASS decides whether a deployment-declared sandbox may auto-admit it
5
+ * (clay's ruling: a declared sandbox admits everything inside it — the boundary crossings park to a
6
+ * human, escape prevention belongs to the sandbox, not to engine re-policing). The class deliberately
7
+ * lives OUTSIDE the public `PermissionResult`: a decision object is caller-supplied data, and a
8
+ * self-reported "this ask is sandbox-local" field would be the same trust hole as the
9
+ * `decisionReason:"safety"` self-declaration this codebase already refuses. Instead, BUILT-IN policy
10
+ * factories register their instances here (private WeakMap brand — nothing a deployment policy can
11
+ * forge by shaping its return value), and everything unregistered reads as `unknown`.
12
+ *
13
+ * Classes and their dominance (STRICT — merge keeps the strongest; only an all-`sandbox_local` set
14
+ * may auto-admit):
15
+ * - `sandbox_local` — an engine-authored "confirm a potentially destructive LOCAL action" ask
16
+ * (name/pattern confirmation lists). Sandbox containment answers it.
17
+ * - `external_authority` — an ask whose whole point is real judgment (the safety-rule policies with
18
+ * `requiresRealApproval`); containment answers nothing about it.
19
+ * - `hook` — a screening-face ask (a deployment's PreToolUse verdict folded to policy
20
+ * form); the face's authority is the deployment's, not the sandbox's.
21
+ * - `mandate` — an ask bound to durable-park semantics (inherited durable mandate /
22
+ * approver-unavailable float); it must reach the park untouched.
23
+ * - `unknown` — everything unregistered, every deployment policy. Fail-closed: never
24
+ * admitted.
25
+ */
26
+ export type AskClass = "sandbox_local" | "external_authority" | "hook" | "mandate" | "unknown";
27
+ /**
28
+ * Brand a built-in policy instance with its ask class (engine factories only — not exported from the
29
+ * package). The `check` binding is LOCKED as a non-writable, non-configurable DATA property before the
30
+ * policy is returned (codex adversarial round 4, HIGH): reading `policy.check` and INVOKING it must
31
+ * name the same callable, atomically. A plain `===` re-read was TOCTOU-able by an accessor — a getter
32
+ * could return the branded function to `policyAskClassOf` and a deployment ask to the gate's
33
+ * invocation. Locking the property makes the callable un-swappable and un-redefinable, so the class
34
+ * travels with the exact decision function. An `check` that is ALREADY accessor-backed (or a frozen
35
+ * object we cannot lock) is refused branding — it reads back `unknown` (fail-closed), never trusted.
36
+ */
37
+ export declare function brandPolicyAskClass<T extends {
38
+ check: unknown;
39
+ }>(policy: T, cls: AskClass): T;
40
+ /** The registered class of a policy instance — `unknown` for anything unregistered, whose `check` is
41
+ * no longer the locked data value, or whose `check` became accessor-backed (fail-closed: a
42
+ * replaced/decorated/getter-shadowed decision function is not the branded one). Reads the property
43
+ * DESCRIPTOR's value (never invokes a getter), so the check is atomic with what the gate will call. */
44
+ export declare function policyAskClassOf(policy: (object & {
45
+ check?: unknown;
46
+ }) | null | undefined): AskClass;
47
+ /** The strongest class in a set (strict dominance — any non-`sandbox_local` member dominates). */
48
+ export declare function dominantAskClass(classes: readonly AskClass[]): AskClass;
@@ -0,0 +1,33 @@
1
+ const RANK = { sandbox_local: 0, external_authority: 1, hook: 2, mandate: 3, unknown: 4 };
2
+ const registry = new WeakMap();
3
+ export function brandPolicyAskClass(policy, cls) {
4
+ const desc = Object.getOwnPropertyDescriptor(policy, "check");
5
+ if (desc === undefined || desc.get !== undefined || desc.set !== undefined)
6
+ return policy;
7
+ try {
8
+ Object.defineProperty(policy, "check", { value: policy.check, writable: false, configurable: false, enumerable: desc.enumerable ?? true });
9
+ }
10
+ catch {
11
+ return policy;
12
+ }
13
+ registry.set(policy, { cls, check: policy.check });
14
+ return policy;
15
+ }
16
+ export function policyAskClassOf(policy) {
17
+ if (policy === null || policy === undefined)
18
+ return "unknown";
19
+ const entry = registry.get(policy);
20
+ if (entry === undefined)
21
+ return "unknown";
22
+ const desc = Object.getOwnPropertyDescriptor(policy, "check");
23
+ if (desc === undefined || desc.get !== undefined || desc.set !== undefined)
24
+ return "unknown";
25
+ return desc.value === entry.check ? entry.cls : "unknown";
26
+ }
27
+ export function dominantAskClass(classes) {
28
+ let strongest = "sandbox_local";
29
+ for (const c of classes)
30
+ if (RANK[c] > RANK[strongest])
31
+ strongest = c;
32
+ return strongest;
33
+ }
@@ -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 6 for org-admission-bearing checkpoints — this worker reads v1 (legacy human), v2
873
- * (resource), v3 (binding human/irreversible_ask), v4 (a pre-164 row, refused only when it carries the
874
- * retired wall-clock allocation), v5 (token-allocation-bearing resource/approval) and v6
875
- * (org-admission freeze state). */
876
- export declare const MAX_SUPPORTED_CHECKPOINT_VERSION = 6;
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` — a partial/mismatched re-supply would silently run the child under a
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` — a partial/mismatched re-supply would silently run the child under a
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", message: string,
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 MAX_SUPPORTED_CHECKPOINT_VERSION = 6;
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,12 @@ 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",
20
+ "memory.challenge_ledger_oversize",
21
+ "memory.control_plane_rebuilt",
22
+ "memory.control_plane_not_corrupt",
17
23
  ]);
18
24
  export function governanceRetryClass(code) {
19
25
  if (Object.prototype.hasOwnProperty.call(GOVERNANCE_CODES, code)) {
@@ -23,3 +29,11 @@ export function governanceRetryClass(code) {
23
29
  return "terminal";
24
30
  return undefined;
25
31
  }
32
+ export const RULE_SYNC_DROP_CODES = {
33
+ invalid_rule_text: "inbound-refused-or-local-quarantined",
34
+ metadata_mismatch: "inbound-refused-or-local-quarantined",
35
+ own_actor_forged: "inbound-refused",
36
+ dot_identity_conflict: "inbound-refused",
37
+ below_gc_frontier: "local-quarantined",
38
+ server_rejected: "local-quarantined",
39
+ };
@@ -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):
@@ -160,8 +161,13 @@ export interface Hooks {
160
161
  * - `"shellGate"` — same tighten-deny, but the tool's irreversibility tier was installed by the COARSE
161
162
  * `TaskSpec.shellGate` doctrine (design/80 D-E), not an explicit per-tool mark.
162
163
  * - `"planMode"` — the design/108 plan-mode write-deny short-circuit (a read-only fidelity gate).
164
+ * - `"org"` (design/182 §7) — the ORG layer: an organization deny rule, or the fail-closed tighten an
165
+ * org-governed deployment applies while it cannot adjudicate against a snapshot (that tighten's ask
166
+ * then resolving to deny). Its own word rather than `"policy"` because the authority is the ORG's
167
+ * published snapshot, not this deployment's `ToolPolicy` — an operator reading "policy" on a call
168
+ * their own policy allowed would be looking in the wrong place.
163
169
  */
164
- export type PermissionDeniedSource = "policy" | "hook" | "safety" | "shellGate" | "planMode" | "classifier";
170
+ export type PermissionDeniedSource = "policy" | "hook" | "safety" | "shellGate" | "planMode" | "classifier" | "org";
165
171
  /** The payload a {@link Hooks.permissionDenied} callback observes (CC-exact fields + `source`). */
166
172
  export interface PermissionDeniedPayload {
167
173
  toolName: string;
@@ -519,6 +525,26 @@ export interface ParkAttemptFailed {
519
525
  /** Why the park could not be minted — a sentence, already worded for a reader. */
520
526
  parkFailed: string;
521
527
  }
528
+ /**
529
+ * design/182 §7 — what the ORG layer answers for ONE call (see `ToolGateInput.orgRules`).
530
+ *
531
+ * The two arms are distinguished by `status`, NEVER by the absence of a verdict: "the org published no
532
+ * rule for this call" and "this deployment cannot see the org's rules" are opposite facts, and reading
533
+ * an empty answer as the first one is exactly the governance fail-open the availability contract exists
534
+ * to prevent.
535
+ */
536
+ export type OrgGateVerdict = {
537
+ status: "available";
538
+ /** The org rule that speaks for this call, if any. Deny outranks ask; there is no allow bucket. */
539
+ verdict?: {
540
+ behavior: "deny" | "ask";
541
+ rule: string;
542
+ };
543
+ } | {
544
+ status: "unavailable";
545
+ /** The overlay's own disclosure lines (engine/deployment-authored), for the operator channel. */
546
+ disclosures: readonly string[];
547
+ };
522
548
  /** Inputs to the two-phase tool gate. `adjudicate`/`resolveAsk` are pre-bound to the task abort signal. */
523
549
  export interface ToolGateInput {
524
550
  /** RB-473 batch 2b — bounded disclosure for a HOST callback that throws inside this gate call's
@@ -712,6 +738,80 @@ export interface ToolGateInput {
712
738
  rule: string;
713
739
  }) => void;
714
740
  };
741
+ /**
742
+ * design/182 §7 — the ORG layer. Present ONLY when the deployment DECLARED org governance (the
743
+ * overlay constructor is the boot gate: a governed declaration with no snapshot provider refuses to
744
+ * boot). Absent ⇒ the whole step does not exist and the decision path is byte-identical.
745
+ *
746
+ * The org layer is the outermost TIGHTENING authority, so it runs after every fold and every engine
747
+ * tighten and before the ask-resolution chain — "org deny > org ask (non-dismissable) > personal
748
+ * allow rule > bare ask". It never loosens: there is structurally no org allow bucket.
749
+ *
750
+ * The `unavailable` arm is the load-bearing one. An org-governed deployment that cannot reach a
751
+ * snapshot fails the WHOLE decision boundary closed — every terminal allow (a deployment-policy
752
+ * allow and the no-policy default allow included) becomes an ask carrying
753
+ * `decisionReason: "org_unavailable"` AND `requiresRealApproval: true`, and both ask→allow seams (the
754
+ * personal-rule lane and the auto-mode classifier) are disarmed. Tightening only the two seams was
755
+ * refuted during design: a call that is ALREADY allow never passes through either seam, so it would
756
+ * have sailed past the org deny that could not be read. The `requiresRealApproval` bit closes the
757
+ * third loosening path — a blanket `onAsk: "allow"` would otherwise clear the synthetic ask.
758
+ */
759
+ orgRules?: {
760
+ /** Adjudicate this call against the current org overlay. MUST NOT throw — a throw is read as
761
+ * `unavailable` (fail-closed), never as "no org rule spoke". */
762
+ adjudicate: (req: ToolCallRequest) => Promise<OrgGateVerdict>;
763
+ /** True iff the reserved question-tool NAME on this leg resolves to the ENGINE's own content-ask
764
+ * tool. It is the exemption's real predicate: the name alone is not, because a caller may mount
765
+ * its own tool under that name when the first-party one is not mounted, and such a tool has
766
+ * arbitrary side effects (codex review, confirmed MED). Absent ⇒ `false` ⇒ the call is governed
767
+ * like any other, which is the fail-closed default. */
768
+ contentAskToolMounted?: boolean;
769
+ /** Disclosure sink for the unavailable arm (trace `permission.org_snapshot_unavailable`). Never
770
+ * affects the outcome. */
771
+ onUnavailable?: (info: {
772
+ toolName: string;
773
+ toolCallId: string;
774
+ message: string;
775
+ }) => void;
776
+ };
777
+ /**
778
+ * F-012 L2 — the SANDBOX ADMISSION leg. Present ONLY when the runner armed the mode: the execution
779
+ * env DECLARES isolation (`RemoteExecutionEnv.capabilities.isolation === true` — a Runner-resolved
780
+ * fact, never caller self-report) AND the park prerequisites hold (durable park configured + a
781
+ * checkpoint store that explicitly declares `redecision.reopen`). Absent ⇒ the whole step does not
782
+ * exist and the decision path is byte-identical to a build without it.
783
+ *
784
+ * Semantics (clay's ruling): inside a declared sandbox, an engine-authored "confirm a potentially
785
+ * destructive local action" ask is answered by containment itself — auto-allow + durable
786
+ * disclosure (`permission.sandbox_admitted`), never a headless deny that pollutes the model.
787
+ * STRICT dominance governs: the leg admits only when EVERY surviving ask on the call is classified
788
+ * `sandbox_local` in the engine's private envelope (deployment-policy asks are `unknown`, hook
789
+ * asks `hook`, marked calls `mandate`, `requiresRealApproval` asks external — none admit), and
790
+ * the call crosses NO declared boundary (`boundaryCapable`: egress-marked tools and
791
+ * protocol-mounted (MCP/A2A) tools — a server's self-report is always boundary-capable). A
792
+ * boundary-crossing ask under the armed mode takes the ordinary durable-park leg below — "park to
793
+ * a human", never an auto-deny.
794
+ */
795
+ sandboxAdmission?: {
796
+ /** The ask classes recorded for this call's fold-layer asks (with their source layers), or
797
+ * `undefined` when no fold layer asked — the gate's own safety tighten is then the only asker. */
798
+ askClassesOf: (toolCallId: string) => ReadonlyArray<{
799
+ cls: AskClass;
800
+ layer: string;
801
+ }> | undefined;
802
+ /** This call's tool crosses a DECLARED boundary (egress axis / protocol-mounted) — never admitted. */
803
+ boundaryCapable: boolean;
804
+ /** The durable disclosure sink — fired on every admission BEFORE the allow is returned. */
805
+ onAdmitted: (info: {
806
+ toolCallId: string;
807
+ toolName: string;
808
+ askClasses: ReadonlyArray<{
809
+ cls: AskClass;
810
+ layer: string;
811
+ }>;
812
+ boundary: "sandbox_internal";
813
+ }) => void;
814
+ };
715
815
  }
716
816
  /**
717
817
  * Fold a {@link Hooks.preToolUse} callback into a {@link ToolPolicy} layer, so a screening face a task