@sema-agent/core 5.28.0 → 5.30.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 (122) hide show
  1. package/CHANGELOG.md +99 -3
  2. package/dist/agents/cascade.d.ts +1 -1
  3. package/dist/agents/observer.d.ts +3 -3
  4. package/dist/agents/peer-admission.d.ts +1 -1
  5. package/dist/agents/send-message-tool.js +2 -0
  6. package/dist/agents/subagent.d.ts +2 -0
  7. package/dist/agents/subagent.js +6 -0
  8. package/dist/agents/teacher.d.ts +1 -1
  9. package/dist/agents/teacher.js +2 -0
  10. package/dist/agents/verify.d.ts +6 -6
  11. package/dist/agents/verify.js +2 -0
  12. package/dist/bench/metrics.d.ts +1 -1
  13. package/dist/brain/retry.d.ts +1 -1
  14. package/dist/core/arg-summary.d.ts +1 -1
  15. package/dist/core/ask-class.d.ts +2 -2
  16. package/dist/core/ask-question.d.ts +1 -1
  17. package/dist/core/ask-question.js +14 -1
  18. package/dist/core/auto-compaction.d.ts +5 -1
  19. package/dist/core/auto-compaction.js +10 -1
  20. package/dist/core/background-shell.d.ts +5 -5
  21. package/dist/core/checkpoint-store.d.ts +65 -19
  22. package/dist/core/checkpoint-store.js +2 -1
  23. package/dist/core/file-snapshot-store.d.ts +1 -1
  24. package/dist/core/hooks.d.ts +15 -4
  25. package/dist/core/hooks.js +8 -2
  26. package/dist/core/human-input-projection.d.ts +2 -2
  27. package/dist/core/memory-admission.d.ts +2 -2
  28. package/dist/core/memory-engine/engine.d.ts +1 -1
  29. package/dist/core/memory-engine/file-backend.d.ts +2 -2
  30. package/dist/core/memory-engine/header-hints.d.ts +1 -1
  31. package/dist/core/memory-engine/layout.d.ts +2 -2
  32. package/dist/core/memory-recall.d.ts +1 -1
  33. package/dist/core/memory.d.ts +2 -2
  34. package/dist/core/oracle-isolation.d.ts +2 -2
  35. package/dist/core/permission-rule-consent.d.ts +1 -1
  36. package/dist/core/permission-rule-model.d.ts +1 -1
  37. package/dist/core/permission-rule-org.d.ts +1 -1
  38. package/dist/core/permission-rule-store.d.ts +1 -1
  39. package/dist/core/permission-rules.js +2 -2
  40. package/dist/core/runner/active-skill-scope.d.ts +1 -1
  41. package/dist/core/runner/memory-consolidation.d.ts +1 -1
  42. package/dist/core/runner/prepare-task.d.ts +29 -8
  43. package/dist/core/runner/prepare-task.js +118 -22
  44. package/dist/core/runner/runtask.d.ts +1 -1
  45. package/dist/core/runner/runtask.js +34 -8
  46. package/dist/core/runner/session-rule-policy.d.ts +4 -3
  47. package/dist/core/runner/teardown-bounded.d.ts +1 -1
  48. package/dist/core/runner/tool-disclosure.d.ts +1 -1
  49. package/dist/core/runner/tool-output-projection.js +1 -1
  50. package/dist/core/scheduler.d.ts +4 -4
  51. package/dist/core/sensitive-path-policy.js +5 -16
  52. package/dist/core/session-policy-store.d.ts +3 -3
  53. package/dist/core/shared-memory/normalize.d.ts +1 -1
  54. package/dist/core/skill-tool-specifier.d.ts +1 -1
  55. package/dist/core/store-contracts/tool-result-store-contract.js +23 -0
  56. package/dist/core/task-notification.d.ts +2 -2
  57. package/dist/core/task-registry-agent.d.ts +9 -9
  58. package/dist/core/task-registry-shared.d.ts +4 -4
  59. package/dist/core/tighten-task-spec.js +18 -0
  60. package/dist/core/tool-errors.d.ts +2 -2
  61. package/dist/core/tool-policy.d.ts +64 -12
  62. package/dist/core/tool-policy.js +68 -8
  63. package/dist/core/tool-result-store.d.ts +2 -2
  64. package/dist/core/tool-result-store.js +3 -1
  65. package/dist/core/trace.d.ts +1 -1
  66. package/dist/core/types.d.ts +68 -13
  67. package/dist/core/untrusted-text.d.ts +6 -0
  68. package/dist/core/untrusted-text.js +1 -0
  69. package/dist/engine/compaction/compaction.d.ts +1 -1
  70. package/dist/engine/harness/messages.d.ts +1 -1
  71. package/dist/engine/harness/types.d.ts +12 -2
  72. package/dist/engine/llm/types.d.ts +1 -1
  73. package/dist/engine/loop/types.d.ts +2 -2
  74. package/dist/engine/session/import-validate.d.ts +1 -1
  75. package/dist/engine/session/log-digest.d.ts +1 -1
  76. package/dist/fixtures/index.d.ts +18 -2
  77. package/dist/fixtures/index.js +11 -0
  78. package/dist/index.d.ts +3 -1
  79. package/dist/index.js +3 -1
  80. package/dist/orchestration/goal.d.ts +1 -1
  81. package/dist/orchestration/run-workflow-tool.d.ts +21 -0
  82. package/dist/orchestration/run-workflow-tool.js +6 -3
  83. package/dist/orchestration/workflow-primitives.d.ts +10 -1
  84. package/dist/orchestration/workflow-primitives.js +12 -1
  85. package/dist/orchestration/workflow-types.d.ts +5 -4
  86. package/dist/orchestration/workflow.js +11 -3
  87. package/dist/prompt-assembly/artifact.d.ts +1 -1
  88. package/dist/prompt-assembly/epoch.js +2 -0
  89. package/dist/prompt-assembly/packs/sema-default.js +2 -2
  90. package/dist/prompt-assembly/types.d.ts +4 -0
  91. package/dist/prompts/default.d.ts +14 -9
  92. package/dist/prompts/default.js +14 -4
  93. package/dist/scenarios/scenario-registry.d.ts +3 -3
  94. package/dist/stores/file/tool-result-store.d.ts +2 -2
  95. package/dist/tools/fs/bash-readonly-classifier.d.ts +23 -2
  96. package/dist/tools/fs/bash-readonly-classifier.js +11 -0
  97. package/dist/tools/fs/fs-bash.d.ts +8 -1
  98. package/dist/tools/fs/fs-bash.js +8 -3
  99. package/dist/tools/fs/fs-pdf.d.ts +1 -1
  100. package/dist/tools/fs/fs-pdf.js +2 -2
  101. package/dist/tools/fs/fs-read.d.ts +1 -1
  102. package/dist/tools/fs/fs-read.js +11 -7
  103. package/dist/tools/fs/fs-search-tools.d.ts +5 -3
  104. package/dist/tools/fs/fs-search-tools.js +15 -8
  105. package/dist/tools/fs/fs-shared.d.ts +6 -2
  106. package/dist/tools/fs/fs-shared.js +8 -3
  107. package/dist/tools/fs/index.d.ts +18 -0
  108. package/dist/tools/fs/index.js +13 -2
  109. package/dist/tools/fs/read-deny.d.ts +105 -0
  110. package/dist/tools/fs/read-deny.js +151 -0
  111. package/dist/tools/fs/read-face.d.ts +43 -0
  112. package/dist/tools/fs/read-face.js +38 -0
  113. package/dist/tools/fs/repo-map.d.ts +3 -1
  114. package/dist/tools/fs/repo-map.js +11 -5
  115. package/dist/tools/fs/safety.d.ts +34 -12
  116. package/dist/tools/fs/safety.js +88 -7
  117. package/dist/tools/fs/search.d.ts +54 -5
  118. package/dist/tools/fs/search.js +103 -21
  119. package/dist/tools/scheduler-tools.d.ts +1 -1
  120. package/dist/tools/task-list.d.ts +1 -1
  121. package/dist/tools/web.d.ts +3 -3
  122. package/package.json +1 -1
@@ -647,6 +647,37 @@ export interface CheckpointState {
647
647
  /** design/44: the hand's read-file state (content hashes), serialized from the hands-toolkit closure
648
648
  * (NOT part of Prepared — §15.2 net-add #8). Without it a resumed `edit_file` is rejected "not read". */
649
649
  readFileState?: Array<[string, ReadEntry]>;
650
+ /**
651
+ * design/199 — the READ-face containment state at suspend (v9,
652
+ * {@link FACE_CHECKPOINT_VERSION}): the RESOLVED face plus the NORMALIZED deny-set ADDITIONS in
653
+ * force (deployment ∪ task layers; the built-in table is engine code, never frozen here — a newer
654
+ * engine's built-ins apply on resume as everywhere else). Written ONLY when the posture is
655
+ * non-default (face "open", or additions present) — an absent section IS the default posture and
656
+ * resumes as roots + built-ins on any worker. Resume folds STRICTER-WINS: face = roots unless the
657
+ * section says open AND the live resolution says open; deny additions = frozen ∪ live (add-only,
658
+ * monotone — a resume can never widen what the cut leg could read).
659
+ *
660
+ * The design's r3/r4 rounds carried a third key (the workspace domain at cut) to evaluate
661
+ * domain-scoped deny entries under their frozen domain; the D-4 ruling (2026-08-13) removed the
662
+ * scoped tier entirely, leaving that key without a consumer — it is deliberately NOT persisted
663
+ * (dead fields in a durable format are the drift surface the liveness gate exists to catch).
664
+ */
665
+ readFace?: {
666
+ face: "open" | "roots";
667
+ denyEntries?: Array<{
668
+ pattern: string;
669
+ caseSensitive: boolean;
670
+ }>;
671
+ /** codex r2 — REDUNDANT governance marker: `true` iff the row's gate was minted WITH a
672
+ * {@link RealApprovalGateBit}. v8's bit-required invariant was version-keyed and v9 (face)
673
+ * outranks v8 in the stamp, so without this a v9 GOVERNED row whose `gate.realApproval` member
674
+ * was dropped in storage would pass pre-CAS and be redeemable on an unwired worker — the exact
675
+ * #130/#120 corruption/downgrade hole, reopened one version up. The resume pre-CAS ladder
676
+ * refuses a v9+ row whose section says `realApproval: true` while the gate carries no
677
+ * well-formed bit (same protection level as v8: a coherent rewrite of BOTH sites remains out of
678
+ * scope for any in-row invariant). Absent = the row was minted ungoverned. */
679
+ realApproval?: true;
680
+ };
650
681
  /**
651
682
  * design/78 Slice-1: the SAFE-tier self-repair loop's durable state (`failureTrace`/`diagnostics`/
652
683
  * `rejectedHypotheses`/`attemptCount`/`oracleTier` — all JSON/`structuredClone`-safe, no fn/Date). Set ONLY
@@ -784,7 +815,7 @@ export interface CheckpointState {
784
815
  /** THIS task's OWN admitted org verdict at suspend (adversarial-review adoption, 2026-08-05):
785
816
  * distinct from {@link admittedOrgScopes} (the incoming PARENT constraint). The resume leg
786
817
  * applies it as the SESSION freeze on the final admitted projection — regardless of origin
787
- * (deployment-origin scopes bypass the chain freeze, never the session freeze) — so a session's
818
+ * (deployment-origin scopes circumvent the chain freeze, never the session freeze) — so a session's
788
819
  * verdict can only NARROW across suspend/resume: re-adjudication still runs every leg (a
789
820
  * revoked grant refuses the resume), but a policy widened mid-session cannot widen THIS
790
821
  * session's mount or the chain its children inherit. Present (possibly EMPTY — an adjudicated
@@ -797,7 +828,7 @@ export interface CheckpointState {
797
828
  * suspend leg did not hold); a scope string = the explicit grant. Present iff
798
829
  * {@link ownAdmittedOrgScopes} is (same adjudication record). */
799
830
  ownAdmittedOrgWriteScope?: string | null;
800
- /** The MONOTONIC org-governance provenance bit (adversarial round 5): `true` when any leg of
831
+ /** The MONOTONIC org-governance provenance bit (falsification-style round 5): `true` when any leg of
801
832
  * the suspended tree ran with an org-admission surface (or inherited the bit). A resume worker
802
833
  * missing the surface still treats the leg as governed — org-shaped scopes respelled outside
803
834
  * the v2 contract are refused, even when every admitted set en route was EMPTY. Absent on
@@ -901,7 +932,7 @@ export declare const RESOURCE_CHECKPOINT_VERSION = 2;
901
932
  * `boundInputHash`) whose enforcement lives ENTIRELY in the resuming worker's resume path. A pre-D-1 worker
902
933
  * (released 1.100.0: `MAX_SUPPORTED`=2, and its resume code has ZERO binding logic) would otherwise resume a
903
934
  * D-1-minted v1 checkpoint and execute the pending tool with NO decision-action verification — the exact
904
- * "approve vendor-A $5 → execute vendor-B $5000" bypass the binding exists to prevent (council BLOCKER #1).
935
+ * "approve vendor-A $5 → execute vendor-B $5000" circumvent the binding exists to prevent (council BLOCKER #1).
905
936
  * Stamping these at **v3 (> the old worker's MAX of 2)** forces a pre-D-1 worker to reject them PRE-CAS
906
937
  * (`unsupported_version`, stays `pending`, retried on a binding-enforcing worker) instead of silently voiding
907
938
  * the binding. A resource_limit suspend keeps stamping v2; a pre-binding (legacy 1.100.0) checkpoint is v1.
@@ -923,7 +954,7 @@ export declare const BINDING_CHECKPOINT_VERSION = 3;
923
954
  */
924
955
  export declare const TOKEN_CHECKPOINT_VERSION = 5;
925
956
  /**
926
- * Org-memory admission state (adversarial round 2, 2026-08-05 — the BINDING_CHECKPOINT_VERSION
957
+ * Org-memory admission state (falsification-style round 2, 2026-08-05 — the BINDING_CHECKPOINT_VERSION
927
958
  * precedent replayed a fourth time): the schema version a suspend stamps when its state carries
928
959
  * org-admission freeze fields (`inheritedGate.admittedOrgScopes` / `ownAdmittedOrgScopes` /
929
960
  * `ownAdmittedOrgWriteScope` — the adjudicated-empty record included). The enforcement — the
@@ -937,7 +968,7 @@ export declare const TOKEN_CHECKPOINT_VERSION = 5;
937
968
  export declare const ORG_ADMISSION_CHECKPOINT_VERSION = 6;
938
969
  /**
939
970
  * F-012 (2026-08-09 — the ORG_ADMISSION_CHECKPOINT_VERSION precedent replayed a fifth time, codex
940
- * adversarial finding): the schema version a suspend stamps when its state carries the F-012 durable
971
+ * falsification-style finding): the schema version a suspend stamps when its state carries the F-012 durable
941
972
  * enforcement fields — `inheritedGate.constraintChain`/`constraintDigest` (the frozen ancestor
942
973
  * projection chain the resume-edit re-adjudication executes) or `delegationProvenance` (the monotonic
943
974
  * provenance aggregate a resume re-seeds). Both enforcements live ENTIRELY in the resuming worker: a
@@ -963,14 +994,29 @@ export declare const F012_CHECKPOINT_VERSION = 7;
963
994
  * stays true: rows whose gate carries no realApproval bit keep their historic stamps everywhere.
964
995
  */
965
996
  export declare const REAL_APPROVAL_CHECKPOINT_VERSION = 8;
997
+ /**
998
+ * design/199 (2026-08-13 — the precedent replayed a seventh time): the schema version a suspend
999
+ * stamps when its state carries the READ-FACE section ({@link CheckpointState.readFace} — the
1000
+ * resolved open face and/or deny-set additions). The enforcement lives ENTIRELY in the resuming
1001
+ * worker (the stricter-wins face fold + the deny-entry union at prepare): a pre-199 worker
1002
+ * (MAX_SUPPORTED=8) would accept the row, ignore the section, and resume the leg under its own
1003
+ * defaults — silently DROPPING the frozen deny additions (a widening), and re-deriving the face
1004
+ * from live config alone. Stamping v9 forces it to reject PRE-CAS (`unsupported_version`, stays
1005
+ * `pending`, retried on an enforcing worker). A row whose task ran the default posture (face roots,
1006
+ * no additions) carries NO section and keeps the historic stamps — such a resume on ANY worker
1007
+ * folds to roots + built-ins, which is exactly the semantics it was cut under.
1008
+ */
1009
+ export declare const FACE_CHECKPOINT_VERSION = 9;
966
1010
  /** The highest {@link Checkpoint.version} `runner.resume` will act on; a higher one is rejected pre-CAS with
967
1011
  * {@link CheckpointError} `unsupported_version` (the checkpoint stays `pending`, retryable on a newer worker).
968
1012
  * Raised to 8 for realApproval-bearing gates — this worker reads v1 (legacy human), v2 (resource), v3
969
1013
  * (binding human/irreversible_ask), v4 (a pre-164 row, refused only when it carries the retired
970
1014
  * wall-clock allocation), v5 (token-allocation-bearing resource/approval), v6 (org-admission freeze
971
- * state), v7 (F-012 constraint-chain / delegation-provenance enforcement state) and v8 (a
972
- * non-budgetable `realApproval` gate bit with its org-origin resume semantics). */
973
- export declare const MAX_SUPPORTED_CHECKPOINT_VERSION = 8;
1015
+ * state), v7 (F-012 constraint-chain / delegation-provenance enforcement state), v8 (a
1016
+ * non-budgetable `realApproval` gate bit with its org-origin resume semantics) and v9 (the
1017
+ * design/199 read-face section: resolved face + deny-set additions, folded stricter-wins on
1018
+ * resume). */
1019
+ export declare const MAX_SUPPORTED_CHECKPOINT_VERSION = 9;
974
1020
  /**
975
1021
  * Read a checkpoint's schema version, defaulting an absent field to **legacy `0`** (a 1.67-era checkpoint
976
1022
  * written before the field existed — it carries no `workspaceHandle`, so resuming it the v1 way is safe).
@@ -1022,7 +1068,7 @@ export declare function debitLedger(prior: ResourceLedger | undefined, slice: {
1022
1068
  * per-slice window / walltime). A resumed slice's effective `maxCostUsd` is `min(window, this)`. NB: `0` is a
1023
1069
  * VALID, exhausted ceiling — NOT "unlimited" (only `undefined` is unlimited). Any spend then immediately trips
1024
1070
  * `overBudget`, and the run loop fails an exhausted resume fast (runtask). Never falsy-test this value (a
1025
- * `if (remaining)` would skip a legitimate 0 ceiling and silently bypass the budget). */
1071
+ * `if (remaining)` would skip a legitimate 0 ceiling and silently circumvent the budget). */
1026
1072
  export declare function remainingBudgetMicroUsd(ledger: ResourceLedger | undefined): number | undefined;
1027
1073
  /** design/164 (the TOKEN sibling of {@link remainingBudgetMicroUsd}): tokens the NEXT slice may still
1028
1074
  * spend = `totalTokens − spentTokens`, never negative. `undefined` when no token total is set (the run is
@@ -1270,7 +1316,7 @@ export interface Checkpoint {
1270
1316
  * `durableApproval` (an unattended safety suspend, a non-ask gate, or a legacy pre-1.389 checkpoint — the
1271
1317
  * consumer then falls back to the human-kind scope-only inference for legacy rows and injects NOTHING for
1272
1318
  * unattended parks, so a spawn that never opted in can never gain the opt-in through a resume). Engine-
1273
- * minted from the task's own spec — a worker/tool cannot forge it (`durableApproval` never rides tool args).
1319
+ * minted from the task's own spec — a worker/tool cannot self-declare it (`durableApproval` never rides tool args).
1274
1320
  */
1275
1321
  durableApproval?: {
1276
1322
  scope: string;
@@ -1442,7 +1488,7 @@ export declare class CheckpointError extends Error {
1442
1488
  * rather than evicting: an already-accepted operator instruction is never dropped to make room —
1443
1489
  * the caller delivers or removes a parked steer first. Nothing is written on this path. */
1444
1490
  | "steering.queue_full"
1445
- /** design/171 §6.3 (adversarial review adoption): the append reused an `inputId` already parked on
1491
+ /** design/171 §6.3 (falsification review adoption): the append reused an `inputId` already parked on
1446
1492
  * this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL payload stays the
1447
1493
  * idempotent no-op a retry needs; a differing one is refused, because swallowing it would lose the
1448
1494
  * second operator's instruction — the exact silent loss the queue replaced. Retry with a fresh id.
@@ -1450,7 +1496,7 @@ export declare class CheckpointError extends Error {
1450
1496
  | "steering.duplicate_input_id"
1451
1497
  /** design/144 §3 (governance-gate purity, fail-closed pre-CAS): a `wake` outcome was supplied for a
1452
1498
  * checkpoint that awaits a PENDING GATE DECISION (tool approval / dry-run review / plan review /
1453
- * resource-limit continue). Wake is a pure un-park verb and must never bypass an approval — resume
1499
+ * resource-limit continue). Wake is a pure un-park verb and must never circumvent an approval — resume
1454
1500
  * the checkpoint through its own decide entry instead (the error message names it). */
1455
1501
  | "wake.gate_pending"
1456
1502
  /** design/144 §3 (anti spin-wake): a `wake` outcome carried no `message` and the checkpoint has no
@@ -1498,7 +1544,7 @@ export declare class CheckpointError extends Error {
1498
1544
  field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy";
1499
1545
  /** WHICH pre-CAS refusal arm fired, where one `code` covers several (requested 2026-08-10: a
1500
1546
  * deployment retry policy needs to tell "a newer worker can redeem this row" from "this row is
1501
- * damaged/forged and no worker ever will" — blanket-retriable and gate-shape heuristics were
1547
+ * damaged/caller-declared and no worker ever will" — blanket-retriable and gate-shape heuristics were
1502
1548
  * both refuted downstream, so the throw site carries the fact it always knew). Closed set;
1503
1549
  * additive and optional like `field` — `code` remains the only REQUIRED discriminant.
1504
1550
  * · `unsupported_version` arms: `"version_newer"` (retryable on a newer worker),
@@ -1507,7 +1553,7 @@ export declare class CheckpointError extends Error {
1507
1553
  * · `invalid_outcome` pre-CAS row-integrity arms: `"real_approval_damaged"`,
1508
1554
  * `"real_approval_forged"`, `"constraint_chain_missing"` (all terminal for the row's
1509
1555
  * current bytes — no worker version redeems a damaged row). */
1510
- reason?: "version_newer" | "env_factory_missing" | "governed_unwired" | "real_approval_damaged" | "real_approval_forged" | "constraint_chain_missing";
1556
+ reason?: "version_newer" | "env_factory_missing" | "governed_unwired" | "real_approval_damaged" | "real_approval_forged" | "read_face_damaged" | "read_face_forged" | "constraint_chain_missing";
1511
1557
  } | undefined;
1512
1558
  constructor(code: "checkpoint.already_exists" | "checkpoint.already_resolved" | "checkpoint.not_found"
1513
1559
  /** `runner.resume` was handed an {@link ResumeOutcome} whose `gate` arm does not match the
@@ -1563,7 +1609,7 @@ export declare class CheckpointError extends Error {
1563
1609
  * rather than evicting: an already-accepted operator instruction is never dropped to make room —
1564
1610
  * the caller delivers or removes a parked steer first. Nothing is written on this path. */
1565
1611
  | "steering.queue_full"
1566
- /** design/171 §6.3 (adversarial review adoption): the append reused an `inputId` already parked on
1612
+ /** design/171 §6.3 (falsification review adoption): the append reused an `inputId` already parked on
1567
1613
  * this checkpoint, but with DIFFERENT content. Re-appending an IDENTICAL payload stays the
1568
1614
  * idempotent no-op a retry needs; a differing one is refused, because swallowing it would lose the
1569
1615
  * second operator's instruction — the exact silent loss the queue replaced. Retry with a fresh id.
@@ -1571,7 +1617,7 @@ export declare class CheckpointError extends Error {
1571
1617
  | "steering.duplicate_input_id"
1572
1618
  /** design/144 §3 (governance-gate purity, fail-closed pre-CAS): a `wake` outcome was supplied for a
1573
1619
  * checkpoint that awaits a PENDING GATE DECISION (tool approval / dry-run review / plan review /
1574
- * resource-limit continue). Wake is a pure un-park verb and must never bypass an approval — resume
1620
+ * resource-limit continue). Wake is a pure un-park verb and must never circumvent an approval — resume
1575
1621
  * the checkpoint through its own decide entry instead (the error message names it). */
1576
1622
  | "wake.gate_pending"
1577
1623
  /** design/144 §3 (anti spin-wake): a `wake` outcome carried no `message` and the checkpoint has no
@@ -1619,7 +1665,7 @@ export declare class CheckpointError extends Error {
1619
1665
  field?: "boundCallId" | "boundInputHash" | "answer" | "settledBy";
1620
1666
  /** WHICH pre-CAS refusal arm fired, where one `code` covers several (requested 2026-08-10: a
1621
1667
  * deployment retry policy needs to tell "a newer worker can redeem this row" from "this row is
1622
- * damaged/forged and no worker ever will" — blanket-retriable and gate-shape heuristics were
1668
+ * damaged/caller-declared and no worker ever will" — blanket-retriable and gate-shape heuristics were
1623
1669
  * both refuted downstream, so the throw site carries the fact it always knew). Closed set;
1624
1670
  * additive and optional like `field` — `code` remains the only REQUIRED discriminant.
1625
1671
  * · `unsupported_version` arms: `"version_newer"` (retryable on a newer worker),
@@ -1628,7 +1674,7 @@ export declare class CheckpointError extends Error {
1628
1674
  * · `invalid_outcome` pre-CAS row-integrity arms: `"real_approval_damaged"`,
1629
1675
  * `"real_approval_forged"`, `"constraint_chain_missing"` (all terminal for the row's
1630
1676
  * current bytes — no worker version redeems a damaged row). */
1631
- reason?: "version_newer" | "env_factory_missing" | "governed_unwired" | "real_approval_damaged" | "real_approval_forged" | "constraint_chain_missing";
1677
+ reason?: "version_newer" | "env_factory_missing" | "governed_unwired" | "real_approval_damaged" | "real_approval_forged" | "read_face_damaged" | "read_face_forged" | "constraint_chain_missing";
1632
1678
  } | undefined);
1633
1679
  }
1634
1680
  /**
@@ -1880,7 +1926,7 @@ export interface CheckpointStore {
1880
1926
  * REJECTED with a typed {@link CheckpointError} `steering.invalid_content` (mirroring runtask.ts's trusted-
1881
1927
  * steer reject) — a dirty steer NEVER enters {@link CheckpointState}, so the persisted state stays clean
1882
1928
  * regardless of `trusted`. (The untrusted RESUME-injection path ALSO sanitizes the text as untrusted data;
1883
- * this persist-time reject is the belt that keeps a forged close tag out of the durable record on BOTH
1929
+ * this persist-time reject is the belt that keeps a caller-declared close tag out of the durable record on BOTH
1884
1930
  * paths.) Text over {@link MAX_PENDING_STEER_CHARS} is rejected with the same code (HRD-APV-4 — the
1885
1931
  * steer is re-delivered into the model context on every resume, so it is capped like every other
1886
1932
  * persisted model-facing field on this row; rejected rather than truncated because cutting an
@@ -136,7 +136,8 @@ export const TOKEN_CHECKPOINT_VERSION = 5;
136
136
  export const ORG_ADMISSION_CHECKPOINT_VERSION = 6;
137
137
  export const F012_CHECKPOINT_VERSION = 7;
138
138
  export const REAL_APPROVAL_CHECKPOINT_VERSION = 8;
139
- export const MAX_SUPPORTED_CHECKPOINT_VERSION = 8;
139
+ export const FACE_CHECKPOINT_VERSION = 9;
140
+ export const MAX_SUPPORTED_CHECKPOINT_VERSION = 9;
140
141
  export function checkpointVersionOf(cp) {
141
142
  return cp.version ?? 0;
142
143
  }
@@ -72,7 +72,7 @@ export interface FileSnapshotStore {
72
72
  /**
73
73
  * 2c session-sync ([277]): STORE a single content-addressed blob — the symmetric WRITE side of {@link getBlob},
74
74
  * for a two-phase PUSH (upload blobs, THEN import the manifest that references them). VERIFIES content-address
75
- * integrity (`sha256(bytes) === hash`) so a corrupt/mismatched upload can never poison the store (a later
75
+ * integrity (`sha256(bytes) === hash`) so a corrupt/mismatched upload can never contaminate the store (a later
76
76
  * getBlob/restore would otherwise return wrong content); a mismatch → `read_failed`, nothing stored. IMMUTABLE +
77
77
  * content-addressed: a repeat putBlob for the same hash is a no-op (the bytes are identical by definition).
78
78
  * Returns a {@link FileSnapshotResult} (NOT void) so the integrity failure is encodable under the never-throw
@@ -8,7 +8,7 @@ import { type AskClass } from "./ask-class.js";
8
8
  *
9
9
  * - {@link Hooks.preToolUse} runs before a tool executes. It may **rewrite** the args (an `allow` with
10
10
  * `updatedInput`), **restrict** the call (`deny`/`ask`), and/or inject `additionalContext`. It is a
11
- * pre-filter only: a hook's `allow` does NOT bypass the tool-policy gate — the policy still runs on
11
+ * pre-filter only: a hook's `allow` does NOT circumvent the tool-policy gate — the policy still runs on
12
12
  * the (possibly rewritten) args and has the final say (load-bearing invariant; see {@link runToolGate}).
13
13
  * - {@link Hooks.postToolUse} runs after a tool executes. It may replace the result content
14
14
  * (`updatedOutput`) and/or append `additionalContext`.
@@ -55,7 +55,7 @@ export interface Hooks {
55
55
  * JSON on the ordinary path, which clones whole; the residual is reachable only through values a
56
56
  * DEPLOYMENT put there (its own rewrite, or driving `runToolGate` directly), and the face and the
57
57
  * deployment are one trust principal — this seam helps a deployment keep its own declaration, it is not
58
- * a boundary against an adversary.
58
+ * a boundary against an untrusted party.
59
59
  *
60
60
  * If a declared-observational face RETURNS SOMETHING ANYWAY, the return is REFUSED, not obeyed: the
61
61
  * call proceeds as if the hook had no opinion (`undefined`), and the deployment's `onError` face is
@@ -142,7 +142,7 @@ export interface Hooks {
142
142
  * plan-mode write-deny. Closes the observation blind spot where a blocked call fires NEITHER postToolUse
143
143
  * NOR postToolUseFailure (R3 scoping). Does NOT fire for:
144
144
  * - a **durable suspend** (the ask path — CC surfaces that via a `can_use_tool` control_request);
145
- * - a **PreToolUse hook deny** (CC-exact: "PreToolUse hook denies bypass canUseTool and are not
145
+ * - a **PreToolUse hook deny** (CC-exact: "PreToolUse hook denies circumvention canUseTool and are not
146
146
  * covered here").
147
147
  * DOES fire for a **PreToolUse hook CRASH** (RB-442, `source:"hook"`): a throwing hook is intercepted by
148
148
  * the ENGINE, which then makes its OWN fail-closed deny decision. The R5 exclusion above covers the
@@ -724,13 +724,20 @@ export interface ToolGateInput {
724
724
  * - `unavailable`/`parse_error` → FAIL-CLOSED: the ask continues down the ORIGINAL chain
725
725
  * (durable suspend / onAsk / headless auto-deny) exactly as if auto mode were absent
726
726
  * (CC `automode-unavailable`/`automode-parsing-error` — "NOT a policy decision").
727
- * SCOPE (declared semantic): this covers EVERY surviving ask, INCLUDING the deterministic egress/
727
+ * SCOPE (declared semantic): this covers every surviving ask INCLUDING the deterministic egress/
728
728
  * irreversibility safety tightens — auto mode is the deployment's explicit choice to let the
729
729
  * classifier be the "explicit ask-resolution" for this session (its rule set covers exactly those
730
730
  * action classes: outbound writes, irreversible operations, and sensitive-value handling). A
731
731
  * deployment that wants human-only
732
732
  * resolution for safety asks simply does not arm auto mode. The classifier can never AUTO-ALLOW by
733
733
  * failing — only an affirmative `<block>no</block>` allows.
734
+ * EXCLUDED from that scope (the ask flows the ORIGINAL chain untouched, exactly as if this field
735
+ * were absent for that one call): an org-constrained ask (design/182 §7.4, either org arm), the
736
+ * reserved question tool, a call MARKED unresolvable (design/153), a hook-raised or hook-promoted
737
+ * ask (`decisionReason:"hook"`), and an ask carrying `matchedAskRule` (an explicit `ask` permission
738
+ * rule matched — #213: a person's standing "ask me each time" is not classifier hesitation). The
739
+ * last two are the same pair the persisted-rule lane refuses, so the gate's two ask→allow seams
740
+ * answer provenance questions identically.
734
741
  */
735
742
  autoMode?: {
736
743
  decider: import("./auto-mode.js").AutoModeDecider;
@@ -769,6 +776,10 @@ export interface ToolGateInput {
769
776
  * tool's own egress/irreversibility marks are structural requirements, not classifier hesitation
770
777
  * ("allow rules silence the classifier's questions, never a mandated one"). When a rule matches
771
778
  * but cannot clear, the surviving ask discloses it (message + `persistedRuleShadowed`).
779
+ * · #213: an ask carrying `matchedAskRule` (an explicit `ask` permission rule matched this call)
780
+ * is never consumed — a person's standing "ask me each time" outranks the memory of one past
781
+ * yes, across lanes exactly as the DSL's own deny > ask > allow order says within one. Same
782
+ * disclosure as the mandated family when a rule matches anyway.
772
783
  * What it DOES consume is the classify-DOCTRINE shell ask (the coarse tier "maybe" — the
773
784
  * don't-ask-again main case this feature exists for) and unmarked bare asks. Consuming such an ask
774
785
  * also skips the park it would have minted; that IS what a standing approval means, and the
@@ -354,7 +354,7 @@ export async function runToolGate(input) {
354
354
  req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
355
355
  input.isMarkedUnresolvable?.(input.event.toolCallId) !== true) {
356
356
  const hit = await input.persistedRules.admits(req).catch(() => undefined);
357
- if (hit !== undefined && persistedRuleMandate === undefined) {
357
+ if (hit !== undefined && persistedRuleMandate === undefined && decision.matchedAskRule === undefined) {
358
358
  decision = {
359
359
  action: "allow",
360
360
  message: `a persisted allow rule (${hit}) covers this call`,
@@ -365,7 +365,11 @@ export async function runToolGate(input) {
365
365
  }
366
366
  else if (hit !== undefined) {
367
367
  const shownRule = inlineUntrusted(hit, 200);
368
- const mandateNoun = persistedRuleMandate === "operator_always" ? "this deployment mandates per-call confirmation for shell commands (shellGate: always)" : "this tool carries egress/irreversibility marks (a mandated confirmation a rule cannot clear)";
368
+ const mandateNoun = persistedRuleMandate === "operator_always"
369
+ ? "this deployment mandates per-call confirmation for shell commands (shellGate: always)"
370
+ : persistedRuleMandate !== undefined
371
+ ? "this tool carries egress/irreversibility marks (a mandated confirmation a rule cannot clear)"
372
+ : "an explicit ask rule matched this call (a person's ask-me-each-time outranks a standing allow rule)";
369
373
  decision = {
370
374
  ...decision,
371
375
  persistedRuleShadowed: shownRule,
@@ -376,6 +380,8 @@ export async function runToolGate(input) {
376
380
  if (input.autoMode &&
377
381
  !orgRealApprovalRequired &&
378
382
  decision.action === "ask" &&
383
+ decision.decisionReason !== "hook" &&
384
+ decision.matchedAskRule === undefined &&
379
385
  req.toolName !== ASK_USER_QUESTION_TOOL_NAME &&
380
386
  input.isMarkedUnresolvable?.(input.event.toolCallId) !== true) {
381
387
  const verdict = await input.autoMode.decider
@@ -18,8 +18,8 @@
18
18
  *
19
19
  * The label goes through the same two neutralizers as the external notification's `from="…"` header
20
20
  * (`inlineUntrusted` + `attrEscape` — one-line, capped, break-out-defused, quote-escaped), so a
21
- * hostile id can never close the `[from "…"]` frame, escape an enclosing `<system-reminder>`, or
22
- * smuggle a fake `(unverified)`-free identity. The projection runs BEFORE trust framing at every
21
+ * untrusted id can never close the `[from "…"]` frame, escape an enclosing `<system-reminder>`, or
22
+ * carry across a fake `(unverified)`-free identity. The projection runs BEFORE trust framing at every
23
23
  * call site, so the annotation always sits INSIDE the frame it attributes (a trusted frame's
24
24
  * reminder wrapper, an untrusted frame's fence) and can never be separated from its text.
25
25
  */
@@ -50,7 +50,7 @@ export interface MemoryAdmissionInput {
50
50
  * request-origin scope outside it refuses the prepare; a deployment-origin scope outside it is
51
51
  * dropped and disclosed; a write grant absent from it collapses to read-only. */
52
52
  priorOwnVerdict: OwnOrgAdmissionVerdict | undefined;
53
- /** The monotonic governance-provenance bit (adversarial round 5): true when this leg's TREE is
53
+ /** The monotonic governance-provenance bit (falsification-style round 5): true when this leg's TREE is
54
54
  * org-governed — the deployment surface is configured here, or ANY ancestor/prior leg recorded
55
55
  * the bit (`InheritedGate.orgAdmissionGoverned`, persisted on checkpoints). Counts as governance
56
56
  * evidence even when every admitted set en route is EMPTY: a governed parent's org-less child,
@@ -82,7 +82,7 @@ export interface MemoryAdmissionOutcome {
82
82
  * (an older-shape chain must never read as "unconstrained"); a top-level leg simply has no chain
83
83
  * constraint. The SESSION freeze (the leg's own suspend-time verdict) is a separate input to
84
84
  * {@link admitMemoryScopes} (`priorOwnVerdict`) — it must constrain the FINAL admitted projection
85
- * regardless of origin (deployment-origin scopes bypass the chain freeze, not the session freeze),
85
+ * regardless of origin (deployment-origin scopes circumvent the chain freeze, not the session freeze),
86
86
  * so it cannot ride this chain-side fold.
87
87
  */
88
88
  export declare function foldAdmissionFreeze(input: {
@@ -37,7 +37,7 @@ export declare const MEMORY_RECALL_DISCIPLINE = "Before answering questions abou
37
37
  * CC-verbatim `# Memory` instruction stays byte-identical to its sha256-pinned capture. Appended
38
38
  * only when a WRITE channel exists (prepare-memory gates it) — the lines it teaches are frontmatter
39
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).
40
+ * would self-declare the very account the three-tier discipline forbids forging).
41
41
  */
42
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.";
43
43
  /**
@@ -323,7 +323,7 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
323
323
  applyPatches(patches: readonly NotePatch[]): Promise<PatchReport>;
324
324
  private txnLockDir;
325
325
  /**
326
- * 对抗复审 L1/L2/L3 — the mkdir mutex, hardened (same shape as layout.ts' acquireSidecarLock, but
326
+ * 证伪式复审 L1/L2/L3 — the mkdir mutex, hardened (same shape as layout.ts' acquireSidecarLock, but
327
327
  * fail-closed timings and an async wait):
328
328
  * - L2: every acquisition writes an OWNER TOKEN (`<lock>/owner`, crypto-random — never a
329
329
  * timestamp) immediately after mkdir. Release only removes the lock while the token is still
@@ -338,7 +338,7 @@ export declare class FileMemoryEngineBackend implements MemoryBackend {
338
338
  * crashed with `remaining stale > waitMs` no longer strands every waiter in an all-throw window.
339
339
  */
340
340
  private acquireTxnLock;
341
- /** 对抗复审 L2 — 提交点归属复验: called immediately before the JOURNAL write (the persistent commit
341
+ /** 证伪式复审 L2 — 提交点归属复验: called immediately before the JOURNAL write (the persistent commit
342
342
  * point). A holder that stalled past the stale deadline (slow disk / GC pause / long txn) and was
343
343
  * stolen from must ABORT — its staged tmps carry no committed state and the stealer's journal
344
344
  * recovery is now authoritative; committing anyway would overwrite the stealer's transaction. */
@@ -11,7 +11,7 @@
11
11
  * This accessor is the ONLY consumer-side reading: pure, lenient, and closed over bad values —
12
12
  * a malformed line reads as FIELD ABSENT, never an error and never a partial adoption. Nothing in
13
13
  * the engine writes these lines; the model does (the preference-discipline paragraph teaches it),
14
- * because an engine-written confirmation would forge the usage evidence it is supposed to record.
14
+ * because an engine-written confirmation would self-declare the usage evidence it is supposed to record.
15
15
  */
16
16
  /** The parsed v2 hints. Every field is absent unless a well-formed line was found. */
17
17
  export interface V2HeaderHints {
@@ -133,7 +133,7 @@ export declare function claimRootScope(controlDir: string, scope: string): strin
133
133
  /** Register `scope` in the durable registry (idempotent) and return its home dir under `memoryDir`.
134
134
  * H5 fail-closed: a dir-name collision with a DIFFERENT registered scope throws — two scopes must
135
135
  * never share one directory (silent cross-scope merge). The collision test has TWO arms:
136
- * - exact: two scopes whose labels are the SAME string (hash collision / forged registry);
136
+ * - exact: two scopes whose labels are the SAME string (hash collision / caller-declared registry);
137
137
  * - case-fold (2026-08-12): two labels differing ONLY in letter case are distinct strings to this
138
138
  * registry yet ONE physical directory on a case-folding volume — cross-scope bleed the exact arm
139
139
  * cannot see. The fold arm compares the FINAL dir labels (ASCII by construction, so toLowerCase
@@ -473,7 +473,7 @@ export declare function appendChallengeEvents(controlDir: string, events: Readon
473
473
  *
474
474
  * `eventCount` rides along for the same reason it does on {@link appendChallengeEvents}: a resolve
475
475
  * APPENDS an event too, so it crosses the size bound exactly as a challenge does (independent
476
- * adversarial review, verified — hanging the disclosure off the challenge writer alone leaves a ledger
476
+ * falsification review, verified — hanging the disclosure off the challenge writer alone leaves a ledger
477
477
  * that crossed the bound on a resolve silent until the next challenge, which may never come).
478
478
  * `undefined` when nothing was appended. */
479
479
  export declare function resolveChallengeEvent(controlDir: string, entryId: string, generation: number, reason: string, now: () => number, requestId?: string): {
@@ -162,7 +162,7 @@ export declare function composeSelectiveBody(manifestText: string, selected: Mem
162
162
  /** A `(scope, id)` composite key for the multi-scope durable de-dup (design/84 Seam A decision 5). Note ids
163
163
  * are only unique WITHIN a scope, so the cross-scope `priorSurfacedKeys`/`surfacedKeys` set MUST be keyed on
164
164
  * both. Core owns the FORMAT so a caller can't drift the wire format (decision 7) — the encoding is OPAQUE
165
- * and length-prefixes the scope so a scope/id containing the separator cannot forge a collision.
165
+ * and length-prefixes the scope so a scope/id containing the separator cannot self-declare a collision.
166
166
  *
167
167
  * ONE-WAY by design (codex MINOR (d)): the key is only ever PRODUCED here (`surfacedKeys`) and compared by
168
168
  * STRING EQUALITY against the caller's persisted set ({@link LayeredRecallOptions.priorSurfacedKeys}, applied
@@ -194,7 +194,7 @@ export interface StructuredNoteInput {
194
194
  export interface MemoryNoteHeader {
195
195
  /** Store contract: ids must be SINGLE-LINE whitespace-free tokens (uuid-like). The manifest renders
196
196
  * `- {id}: …` lines and the side-query matches selected ids by string equality — an id containing
197
- * a newline could forge manifest lines, and a renderer-side rewrite would break the equality match,
197
+ * a newline could self-declare manifest lines, and a renderer-side rewrite would break the equality match,
198
198
  * so the contract sits on the store (search 1.95.1 review #2). */
199
199
  id: string;
200
200
  /** A one-line summary used as the relevance signal (derived from the note's first sentence when untyped). */
@@ -306,7 +306,7 @@ export declare function enforceSecretWriteGate(body: string): void;
306
306
  * (rendered as `[[name]]` cross-references) and `description` (rendered into the recall manifest + injected
307
307
  * into the side-query sub-prompt) are ALSO persisted and surfaced, so a secret hidden there
308
308
  * (`appendStructured({ body: "safe", description: "sk_live_…" })`) would otherwise be durably stored and
309
- * leaked into every later same-scope manifest — a real bypass of the body-only scan. `links` slugs are NOT
309
+ * leaked into every later same-scope manifest — a real circumvention of the body-only scan. `links` slugs are NOT
310
310
  * scanned: they are short `[[name]]`-style reference tokens (no credential body shape clears the
311
311
  * conservative detector), and scanning them adds FP surface for no realistic gain. Fail-closed: the first
312
312
  * field that carries a high-confidence credential throws {@link MemoryGateError}`("memory.secret_detected")`.
@@ -46,7 +46,7 @@ export interface OracleIsolationVerdict {
46
46
  export interface GraderEnv {
47
47
  /** The isolated env the oracle grades in (service-provisioned; a {@link import("./remote-env.js").RemoteExecutionEnv} in production). */
48
48
  env: ExecutionEnv;
49
- /** Paths the oracle must be protected behind — probed for writability through the WORKER's bash bypass surface. */
49
+ /** Paths the oracle must be protected behind — probed for writability through the WORKER's bash circumvention surface. */
50
50
  immutableOraclePaths: string[];
51
51
  /** Provenance brand — ONLY the control-plane factory sets this. Never derived from worker state. */
52
52
  provenance: "control_plane";
@@ -72,7 +72,7 @@ export interface AssertOracleIsolationOptions {
72
72
  * `candidate_only`); the class is what gates auto-accept (the 2-of-3 asymmetry).
73
73
  *
74
74
  * **(C) BASH WRITE-PROBE (necessary-not-sufficient, cheap early signal)** — for each `immutableOraclePath`, run
75
- * a write probe through the WORKER env's `exec` (the bash bypass surface — see {@link probeCommand}), against
75
+ * a write probe through the WORKER env's `exec` (the bash circumvention surface — see {@link probeCommand}), against
76
76
  * the path canonicalized via {@link canonicalizeTarget}. The probe PASSES only if the write returns a non-zero
77
77
  * exit code or an exec error (read-only mount / permission denied / not supported). Any SUCCESS (RC 0) ⇒ the
78
78
  * oracle dir is writable from the worker ⇒ `protected:false`. A probe THROW / abort / timeout ⇒ fail-closed
@@ -23,7 +23,7 @@
23
23
  * ## The boundary this does NOT claim
24
24
  *
25
25
  * The confirmation transfer is an in-process call. A deployment must reach it only from its authenticated
26
- * approval channel, and a deployment that shares filesystem credentials with the user can bypass all of
26
+ * approval channel, and a deployment that shares filesystem credentials with the user can circumvent all of
27
27
  * this by editing a file backend's file. That is the settings-file trust model, stated rather than
28
28
  * defended against: for a file backend, host = user, no more and no less.
29
29
  */
@@ -18,7 +18,7 @@
18
18
  *
19
19
  * The floor is `parseLeadingCommandName` — the one simple-command parser already shared by the
20
20
  * read-only classifier, the reversibility probe, the coarse command-name policy and the skill tool
21
- * specifier. A second tokenizer would drift, and drift on a loosening face shows up as a bypass rather
21
+ * specifier. A second tokenizer would drift, and drift on a loosening face shows up as a circumvention rather
22
22
  * than as a test failure.
23
23
  *
24
24
  * ## Normalization order is load-bearing
@@ -203,7 +203,7 @@ export interface EffectivePermissionRule {
203
203
  /**
204
204
  * design/182 §7.3 — the introspection face: every personal rule with its effective status under the org
205
205
  * overlay. An org deny does not delete a personal rule — the personal rule is the user's approval
206
- * record, and the org's power is to OVERRIDE it, not to forge its removal — so a shadowed rule reports
206
+ * record, and the org's power is to OVERRIDE it, not to self-declare its removal — so a shadowed rule reports
207
207
  * `shadowed-by-org` and comes back by itself when the org deny is withdrawn. A rule whose every add is
208
208
  * tombstoned reports `removed` (its tombstone identity is still visible in the store).
209
209
  *
@@ -159,7 +159,7 @@ export interface RuleSyncJoinDelta {
159
159
  * What a write may say. Authorization discriminates on the DELTA SHAPE, not on a full snapshot: only the
160
160
  * add arm can introduce a dot, and the delete arm carries a tombstone and no adds. A backend additionally
161
161
  * REFUSES at runtime any delete that would introduce a new add dot — structure and runtime check together,
162
- * so "pick the delete arm and smuggle an add" is neither expressible nor accepted. The `sync-join` arm's
162
+ * so "pick the delete arm and carry across an add" is neither expressible nor accepted. The `sync-join` arm's
163
163
  * safety does not rest on hiding the type (ruled 2026-08-10: the backend contract IS exported for
164
164
  * out-of-repo store twins) — it rests on every inbound record inside it passing the single validator
165
165
  * again AT THE BACKEND: the fifth door of design/179 §4's validator list closes here, not at the
@@ -287,11 +287,11 @@ export function createPermissionRulePolicy(rules, opts) {
287
287
  return { action: "deny", message: ruleMessage("denied", paramDeny.ruleText, paramDeny.source) };
288
288
  }
289
289
  if (entry.bare.ask) {
290
- return { action: "ask", message: ruleMessage("flagged", entry.bare.ask.ruleText, entry.bare.ask.source) };
290
+ return { action: "ask", message: ruleMessage("flagged", entry.bare.ask.ruleText, entry.bare.ask.source), matchedAskRule: entry.bare.ask.ruleText };
291
291
  }
292
292
  const paramAsk = matchParamRules(entry.param.ask, req.args, caps.maxScalarValueChars);
293
293
  if (paramAsk) {
294
- return { action: "ask", message: ruleMessage("flagged", paramAsk.ruleText, paramAsk.source) };
294
+ return { action: "ask", message: ruleMessage("flagged", paramAsk.ruleText, paramAsk.source), matchedAskRule: paramAsk.ruleText };
295
295
  }
296
296
  if (entry.bare.allow) {
297
297
  return { action: "allow" };
@@ -34,7 +34,7 @@ export declare class ActiveSkillScope {
34
34
  * - DENY any tool not in the INTERSECTION of every active frame's `allowTools`.
35
35
  * - For a WRITE tool with an `allowPaths` constraint on ANY active frame, DENY a write whose
36
36
  * canonicalized target is outside every listed prefix (reuses {@link canonicalizeTarget}, so a
37
- * symlink can't smuggle a write outside the allowed paths).
37
+ * symlink can't carry across a write outside the allowed paths).
38
38
  * - A frame whose manifest could not be resolved (`kind:"unresolved"`) → DENY-ALL (fail-closed: no
39
39
  * allowlist means nothing is allowed — NOT the bare task policy).
40
40
  * When NO frame is active, the policy returns `allow` for everything (it only ever subtracts), leaving
@@ -27,7 +27,7 @@ export declare const DEFAULT_CONSOLIDATION_MAX_NOTES = 50;
27
27
  export declare const DEFAULT_CONSOLIDATION_TIMEOUT_SEC = 30;
28
28
  /** Normalize a note body for the EXACT-restatement check in the `<lo` near-dup cheap path. Case- + whitespace-
29
29
  * insensitive ONLY (lower-case, collapse whitespace runs, trim); it PRESERVES every symbol/punctuation so it can
30
- * NOT false-equate semantically-distinct facts. (service [365] adversarial: the earlier `[^\p{L}\p{N}]`-stripping
30
+ * NOT false-equate semantically-distinct facts. (service [365] falsification-style: the earlier `[^\p{L}\p{N}]`-stripping
31
31
  * normalization made "C#"≡"C", "delta +5"≡"delta -5", "100%"≡"100" — and a textual-CONTAINMENT test on top of it
32
32
  * also mis-judged "rate limit 1000" ⊇ "rate limit 100" (word boundary). A fire-on-every-note, no-LLM path must not
33
33
  * make a semantic-subset call, so the cheap path now folds ONLY an exact restatement; every real near-dup defers