@sema-agent/core 7.31.0 → 7.32.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 (99) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/dist/agents/inherited-tool-pool.d.ts +28 -0
  3. package/dist/agents/inherited-tool-pool.js +15 -0
  4. package/dist/agents/list-agents-tool.d.ts +7 -0
  5. package/dist/agents/list-agents-tool.js +9 -2
  6. package/dist/agents/peer-session-drain.d.ts +2 -0
  7. package/dist/agents/peer-session-drain.js +1 -0
  8. package/dist/agents/send-message-tool.d.ts +17 -0
  9. package/dist/agents/send-message-tool.js +11 -4
  10. package/dist/agents/subagent.d.ts +10 -1
  11. package/dist/agents/subagent.js +24 -8
  12. package/dist/core/ask-origin.d.ts +15 -6
  13. package/dist/core/ask-origin.js +15 -9
  14. package/dist/core/background-shell.d.ts +45 -0
  15. package/dist/core/background-shell.js +13 -0
  16. package/dist/core/checkpoint-store.d.ts +5 -0
  17. package/dist/core/checkpoint-store.js +3 -2
  18. package/dist/core/engine-notice.d.ts +9 -0
  19. package/dist/core/gate-lanes.js +1 -1
  20. package/dist/core/governance-codes.d.ts +1 -1
  21. package/dist/core/governance-codes.js +5 -0
  22. package/dist/core/hooks.d.ts +2 -0
  23. package/dist/core/memory-engine/engine.d.ts +10 -1
  24. package/dist/core/memory-engine/engine.js +30 -6
  25. package/dist/core/memory-engine/file-backend.d.ts +15 -0
  26. package/dist/core/memory-engine/file-backend.js +53 -2
  27. package/dist/core/memory-engine/layout.d.ts +29 -4
  28. package/dist/core/memory-engine/layout.js +24 -4
  29. package/dist/core/read-root-grant-screen.d.ts +23 -0
  30. package/dist/core/read-root-grant-screen.js +17 -0
  31. package/dist/core/runner/contracts.d.ts +1 -1
  32. package/dist/core/runner/prepare-artifact.d.ts +12 -7
  33. package/dist/core/runner/prepare-artifact.js +10 -8
  34. package/dist/core/runner/prepare-ask-lane.d.ts +3 -0
  35. package/dist/core/runner/prepare-ask-lane.js +5 -1
  36. package/dist/core/runner/prepare-boundary-parks.js +5 -5
  37. package/dist/core/runner/prepare-delegation-surface.d.ts +18 -0
  38. package/dist/core/runner/prepare-delegation-surface.js +6 -25
  39. package/dist/core/runner/prepare-hands-readface.d.ts +12 -89
  40. package/dist/core/runner/prepare-hands-readface.js +14 -48
  41. package/dist/core/runner/prepare-inherited-gate.d.ts +3 -1
  42. package/dist/core/runner/prepare-inherited-gate.js +2 -1
  43. package/dist/core/runner/prepare-park-ask.js +6 -4
  44. package/dist/core/runner/prepare-policy-chain.js +4 -0
  45. package/dist/core/runner/prepare-task.js +5 -3
  46. package/dist/core/runner/read-root-grant.d.ts +66 -0
  47. package/dist/core/runner/read-root-grant.js +77 -0
  48. package/dist/core/runner/run-notification-lane.d.ts +1 -1
  49. package/dist/core/runner/run-settle-and-teardown.js +3 -9
  50. package/dist/core/runner/runtask.js +1 -1
  51. package/dist/core/runner/send-message-continuation.d.ts +45 -0
  52. package/dist/core/runner/send-message-continuation.js +27 -0
  53. package/dist/core/runner/session-read-file-states.d.ts +91 -0
  54. package/dist/core/runner/session-read-file-states.js +43 -0
  55. package/dist/core/secret-env.d.ts +1 -1
  56. package/dist/core/secret-env.js +7 -2
  57. package/dist/core/store-contracts/checkpoint-store-contract.d.ts +2 -0
  58. package/dist/core/store-contracts/checkpoint-store-contract.js +19 -0
  59. package/dist/core/task-registry-monitor.js +3 -0
  60. package/dist/core/task-registry-shared.d.ts +16 -3
  61. package/dist/core/task-registry.d.ts +65 -29
  62. package/dist/core/task-registry.js +78 -28
  63. package/dist/core/task-spec.d.ts +5 -3
  64. package/dist/core/tool-policy.d.ts +23 -0
  65. package/dist/core/tool-policy.js +15 -4
  66. package/dist/core/tool-spec.d.ts +2 -0
  67. package/dist/engine/execution-env/node-execution-env.d.ts +13 -3
  68. package/dist/engine/execution-env/node-execution-env.js +26 -0
  69. package/dist/index.d.ts +4 -3
  70. package/dist/index.js +2 -1
  71. package/dist/scenarios/full-body.d.ts +5 -2
  72. package/dist/scenarios/full-body.js +14 -7
  73. package/dist/tools/fs/fs-bash-background-text.d.ts +16 -0
  74. package/dist/tools/fs/fs-bash-background-text.js +22 -0
  75. package/dist/tools/fs/fs-bash.d.ts +6 -0
  76. package/dist/tools/fs/fs-bash.js +9 -27
  77. package/dist/tools/fs/fs-read.d.ts +2 -2
  78. package/dist/tools/fs/fs-search-tools.d.ts +3 -2
  79. package/dist/tools/fs/index.d.ts +8 -1
  80. package/dist/tools/fs/index.js +2 -3
  81. package/dist/tools/fs/repo-map.d.ts +2 -1
  82. package/dist/tools/fs/safety.d.ts +3 -1
  83. package/dist/tools/fs/safety.js +2 -1
  84. package/dist/tools/fs/shell-read-check.d.ts +3 -0
  85. package/dist/tools/fs/shell-read-check.js +1 -1
  86. package/dist/tools/fs/shell-read-establish.d.ts +3 -1
  87. package/dist/tools/fs/shell-read-establish.js +2 -2
  88. package/dist/tools/fs/shell-read-plan.d.ts +2 -2
  89. package/dist/tools/fs/shell-read-plan.js +15 -10
  90. package/dist/tools/fs/shell-read-word.d.ts +5 -4
  91. package/dist/tools/fs/shell-read-word.js +3 -2
  92. package/dist/tools/monitor.d.ts +5 -4
  93. package/dist/tools/monitor.js +5 -4
  94. package/dist/tools/todo.d.ts +3 -3
  95. package/dist/tools/todo.js +5 -4
  96. package/package.json +1 -1
  97. package/test/export-surface.snapshot.json +13 -1
  98. package/wire-changes/core@7.31.1.json +189 -0
  99. package/wire-changes/core@7.32.0.json +850 -0
@@ -145,14 +145,14 @@ export function establishShellReadsLexically(plan, boundary) {
145
145
  }
146
146
  return { plan, reads, nested, roots: boundary.roots };
147
147
  }
148
- export function createShellReadEstablisher(env, readRoots) {
148
+ export function createShellReadEstablisher(env, readRoots, opts) {
149
149
  const lexicalOnly = boundaryIsTheSandbox(env);
150
150
  const resolverIsAuthoritative = canonicalPathIsAuthoritative(env);
151
151
  const family = pathFamilyOf({ root: readRoots[0] ?? "/" });
152
152
  const SEP = family === "posix" ? /\// : /[\\/]/;
153
153
  const TRAILING_SEP = family === "posix" ? /\/+$/ : /[\\/]+$/;
154
154
  const joinUnder = (dir, name) => `${dir.replace(TRAILING_SEP, "")}/${name}`;
155
- let rootsOnce;
155
+ let rootsOnce = opts?.rootsCanonical === true ? Promise.resolve(readRoots) : undefined;
156
156
  const canonicalRoots = async (signal) => {
157
157
  rootsOnce ??= (async () => {
158
158
  const out = [];
@@ -62,8 +62,8 @@ export interface ReadCandidate {
62
62
  * A deny row is a floor, so the judge resolves the spelling against these for deny rows ONLY — never to
63
63
  * establish a read. `cd sub && cd $'\x2e' && cat <deny row under sub>` (bash: `cd .`) is refused through it. */
64
64
  readonly floorBases?: readonly string[];
65
- /** A home word the plan does not place (the text may rebind `HOME`, or it sits in a value): spelled with the DECLARED
66
- * home. A deny row is a floor: the judge reads it for deny rows ONLY, never to establish a read. */
65
+ /** A home word the plan does not place (the text may rebind `HOME`, it sits in a value, or it is a literal tilde the
66
+ * program may expand itself): spelled with the DECLARED home. The judge reads it for deny rows ONLY. */
67
67
  readonly floorSpelled?: string;
68
68
  /** A deny-floor shadow of a `floorSpelled` spelling, established as a read and judged for deny rows only. */
69
69
  readonly denyOnly?: true;
@@ -193,7 +193,7 @@ function segmentFacts(segment, p, decl, home, nestedPlan) {
193
193
  unresolvedRaw.add(w.raw);
194
194
  }
195
195
  const homeWithheld = home === undefined && decl.homeDir !== undefined;
196
- const floor = (c) => { const f = c.expands === "unresolvable" && decl.homeDir !== undefined ? spelledWithHome(c.spelled, c.raw, decl.homeDir) : undefined; return f === undefined ? c : { ...c, floorSpelled: f }; };
196
+ const floor = (c) => { const f = (c.expands === "unresolvable" || c.spelled.startsWith("./~")) && decl.homeDir !== undefined ? spelledWithHome(c.spelled, c.raw, decl.homeDir) : undefined; return f === undefined ? c : { ...c, floorSpelled: f }; };
197
197
  const unsettle = (c) => floor((unresolvedRaw.has(c.raw) || (homeWithheld && c.spelled.startsWith("~"))) && c.expands !== "unresolvable" ? { ...c, expands: "unresolvable" } : c);
198
198
  const move = facts.baseMove;
199
199
  return {
@@ -268,9 +268,8 @@ function segmentFactsRead(segment, p, decl, home, nestedPlan) {
268
268
  const basename = program?.basename;
269
269
  const listed = basename !== undefined && (decl.readers.has(basename) || basename === "cd");
270
270
  const verb = program === undefined ? undefined : { name: program.name, basename: program.basename, listed };
271
- const envOpens = basename !== undefined && !NO_PATH_OPERAND_COMMANDS.has(basename);
272
271
  for (const k of pos.assignments)
273
- pushAssignment(reads, toks[k].word, "env_assignment", envOpens);
272
+ pushAssignment(reads, toks[k].word, "env_assignment");
274
273
  for (const l of pos.launchers) {
275
274
  const r = toks[l.index];
276
275
  if (isPathShapedToken(r.folded))
@@ -313,7 +312,7 @@ function segmentFactsRead(segment, p, decl, home, nestedPlan) {
313
312
  continue;
314
313
  }
315
314
  if (r.word.assignment !== undefined) {
316
- pushAssignment(reads, r.word, "env_assignment", envOpens);
315
+ pushAssignment(reads, r.word, "env_assignment");
317
316
  continue;
318
317
  }
319
318
  reads.push(bare(r, "unlisted_word"));
@@ -462,7 +461,7 @@ function dedupe(reads) {
462
461
  const seen = new Set();
463
462
  const out = [];
464
463
  for (const c of reads) {
465
- const key = `${c.provenance}\u0000${c.spelled}\u0000${c.expands}\u0000${c.denyOnly === true}`;
464
+ const key = `${c.provenance}\u0000${c.spelled}\u0000${c.expands}\u0000${c.raw}\u0000${c.denyOnly === true}`;
466
465
  if (seen.has(key))
467
466
  continue;
468
467
  seen.add(key);
@@ -488,7 +487,7 @@ function redirectFact(r, home) {
488
487
  return { op: r.op, direction: "in" };
489
488
  return { op: r.op, direction: "out", target: bare(readsOf([r.target], home)[0], "write_redirect_target") };
490
489
  }
491
- function pushAssignment(reads, w, provenance, opensValue = true) {
490
+ function pushAssignment(reads, w, provenance) {
492
491
  const a = w.assignment;
493
492
  if (a === undefined)
494
493
  return;
@@ -498,13 +497,19 @@ function pushAssignment(reads, w, provenance, opensValue = true) {
498
497
  const named = a.value.folded === "" || (a.op === "+=" ? expandsTilde : !settled) ? undefined : value;
499
498
  if (a.value.folded === "" && settled)
500
499
  return;
501
- const v = { spelled: a.value.folded, raw: a.bare ? a.value.raw : w.raw, provenance };
500
+ const v = { spelled: value, raw: a.bare ? a.value.raw : w.raw, provenance };
502
501
  const unsettled = provenance !== "env_assignment" ? { ...v, expands: "unresolvable" } : v.spelled.startsWith("~") && !v.spelled.includes(":") ? { ...v, expands: expandsOf(a.value) } : undefined;
503
- if (!settled && opensValue && unsettled !== undefined)
502
+ if (!settled && unsettled !== undefined)
504
503
  reads.push(unsettled);
505
504
  const substitutes = a.value.expansions.some((p) => p.expansion?.kind === "command" || p.expansion?.kind === "process" || p.expansion?.kind === "arithmetic");
506
505
  if (named !== undefined && !substitutes)
507
506
  reads.push({ spelled: named, raw: w.raw, provenance, expands: expandsOf(w) });
507
+ if (a.value.folded.includes(":"))
508
+ for (const m of a.value.folded.split(":"))
509
+ if (m !== "") {
510
+ const r = readOfText(m);
511
+ reads.push({ spelled: r.folded, raw: r.raw, provenance, expands: r.expands, denyOnly: true });
512
+ }
508
513
  if (a.form === "compound")
509
514
  for (const e of compoundArrayElements(a.value))
510
515
  reads.push({ spelled: e.folded, raw: e.raw, provenance, expands: e.expands, ...(e.denyOnly === true ? { denyOnly: true } : {}) });
@@ -527,7 +532,7 @@ function unlistedWords(reads, toks, from, launcherIndices, provenance, program)
527
532
  expanding.add(j);
528
533
  reads.push(bare(toks[j], j === argvStart ? "program_path" : provenance));
529
534
  if (j < argvStart)
530
- pushAssignment(reads, toks[j].word, "env_assignment", slotVerb !== undefined && !noPathOperands);
535
+ pushAssignment(reads, toks[j].word, "env_assignment");
531
536
  else if (j > argvStart && !declaredNotPaths)
532
537
  pushAssignment(reads, toks[j].word, provenance);
533
538
  }
@@ -605,7 +610,7 @@ function unlistedWords(reads, toks, from, launcherIndices, provenance, program)
605
610
  }
606
611
  reads.push(bare(r, j === argvStart ? "program_path" : provenance));
607
612
  if (j < argvStart)
608
- pushAssignment(reads, r.word, "env_assignment", slotVerb !== undefined && !noPathOperands);
613
+ pushAssignment(reads, r.word, "env_assignment");
609
614
  else if (!declaredNotPaths)
610
615
  pushAssignment(reads, r.word, provenance);
611
616
  }
@@ -25,10 +25,11 @@ export declare function readsOf(words: readonly LexedWord[], home: string | unde
25
25
  export declare const rewritten: (r: Read) => boolean;
26
26
  /** The reading of a text that came out of a word (a sed script's file operand): one shell word, or unresolvable. */
27
27
  export declare function readOfText(text: string): Read;
28
- /** A home-rooted candidate spelled with `home`: a leading `~` / `~/` (a literal tilde is spelled `./~` before this
29
- * point, so a leading `~` IS the expansion), or a `$HOME` / `${HOME}` word as {@link homeExpansionOf} reads it —
30
- * read off the VALUE's pieces when the word is assignment-shaped (`A="$HOME/x"`, `dd if="$HOME/x"`: the candidate is
31
- * the value, quotes kept); `undefined` for any other (`~user`, a payload, a word that is no home word). */
28
+ /** A home-rooted candidate spelled with `home`: a leading `~` / `~/` — the shell's expansion, or the literal-tilde
29
+ * spelling `./~` of one it leaves alone (quoted, escaped, after an option's `=`), which the program it reaches may
30
+ * expand itself — or a `$HOME` / `${HOME}` word as {@link homeExpansionOf} reads it, read off the VALUE's pieces when
31
+ * the word is assignment-shaped (`A="$HOME/x"`, `dd if="$HOME/x"`: the candidate is the value, quotes kept);
32
+ * `undefined` for any other (`~user`, a word that is no home word). */
32
33
  export declare function spelledWithHome(spelled: string, raw: string, home: string): string | undefined;
33
34
  /** A function definition's head names nothing that runs here and is dropped from a segment's tokens, in place:
34
35
  * `NAME ( )` (an identifier), and `function NAME [( )]` — a bare `function` at command position, a name of any
@@ -20,8 +20,9 @@ export function readOfText(text) {
20
20
  return { word, raw: text, folded: text, expands: expandsOf(word) };
21
21
  }
22
22
  export function spelledWithHome(spelled, raw, home) {
23
- if (spelled.startsWith("~"))
24
- return /^~(?:\/|$)/.test(spelled) ? `${home}${spelled.slice(1)}` : undefined;
23
+ const tilde = spelled.startsWith("./~") ? spelled.slice(2) : spelled;
24
+ if (tilde.startsWith("~"))
25
+ return /^~(?:\/|$)/.test(tilde) ? `${home}${tilde.slice(1)}` : undefined;
25
26
  if (!raw.includes("HOME"))
26
27
  return undefined;
27
28
  const w = readOfText(raw).word;
@@ -73,10 +73,11 @@ export interface MonitorToolOptions {
73
73
  * spawns a shell command too, so what it runs is what the station judged, re-established before the watch
74
74
  * spawns. Absent ⇒ no station judged this mount; nothing is re-established. */
75
75
  shellReadAdmission?: ShellReadAdmission;
76
- /** RB-465-b (HRD-PTL-3 残件): spec-level retain (`TaskSpec.retainBackgroundProcesses`) — the SECOND
77
- * retain lane, OR-composed with the env capability at the teardown sweep. Without this bit the mount
78
- * could only see the env lane, so a deployment retaining per spec still got the "session ends it"
79
- * wording in the persistent-watch receipt. Purely a RECEIPT input — enforcement stays at the sweep. */
76
+ /** RB-465-b (HRD-PTL-3 残件): spec-level retain (`TaskSpec.retainBackgroundProcesses`) — the task lane of
77
+ * THE retain predicate (`retainsBackgroundProcesses`), OR-composed with the env capability. Read twice,
78
+ * by the same predicate: the persistent-watch receipt's wording, and the row's `retained` mark at
79
+ * registration (so the session reap and the exit-boundary hard face judge the row the way the receipt
80
+ * described it). */
80
81
  retainBackgroundProcesses?: boolean;
81
82
  /** The task root in the SAME canonical spelling `createBashTool` is built with, so a dead-cwd self-heal
82
83
  * lands both tools on one directory. Absent ⇒ the diagnosis says it has no reset target, rather than
@@ -1,6 +1,6 @@
1
1
  import { Type } from "typebox";
2
2
  import { defineTool, errorResult } from "../core/tools.js";
3
- import { hasBackgroundShell } from "../core/background-shell.js";
3
+ import { envDeclaresRetain, hasBackgroundShell, retainsBackgroundProcesses } from "../core/background-shell.js";
4
4
  import { MONITOR_DEFAULT_TIMEOUT_MS, MONITOR_MAX_TIMEOUT_MS, } from "../core/task-registry.js";
5
5
  import { toolFace } from "../core/tool-catalog-entries.js";
6
6
  import { isDeadCwd, diagnoseDeadCwd } from "./fs/dead-cwd.js";
@@ -101,6 +101,7 @@ export function createMonitorTool(env, opts) {
101
101
  persistent: isPersistent,
102
102
  ...(sessionScoped ? { sessionScoped: true } : {}),
103
103
  ...(isPersistent ? {} : { timeoutMs }),
104
+ ...(opts.retainBackgroundProcesses !== undefined ? { retainBackgroundProcesses: opts.retainBackgroundProcesses } : {}),
104
105
  ...(onNotify !== undefined ? { onEvent: (n) => onNotify(n, { priority: "next" }) } : {}),
105
106
  ...(opts.timers !== undefined ? { timers: opts.timers } : {}),
106
107
  ...(opts.batchWindowMs !== undefined ? { batchWindowMs: opts.batchWindowMs } : {}),
@@ -135,12 +136,12 @@ export function createMonitorTool(env, opts) {
135
136
  const persistentBudget = bgTimeout !== undefined
136
137
  ? `the execution environment's background time budget ends the watch after ${bgTimeout}s`
137
138
  : `the execution environment's background time budget still applies`;
138
- const retainedByEnv = env.backgroundCapabilities.retainBackgroundProcesses === true;
139
+ const retainedByEnv = envDeclaresRetain(env);
139
140
  const retainedBySpec = opts.retainBackgroundProcesses === true;
140
- const retained = retainedByEnv || retainedBySpec;
141
+ const retained = retainsBackgroundProcesses({ env: retainedByEnv, task: retainedBySpec });
141
142
  const persistentAnchor = sessionScoped
142
143
  ? retained
143
- ? ". This run retains background processes (environment capability or task spec), so at the run teardown the watch is marked retained and " +
144
+ ? ". This run retains background processes (environment capability or task spec), so the watch is retained and " +
144
145
  "survives the session's release: only your TaskStop, the command's own exit, or that budget end it."
145
146
  : "; it runs until then, until you stop it, or until the session ends."
146
147
  : retained
@@ -2,9 +2,9 @@ import type { ToolSpec } from "../core/types.js";
2
2
  /**
3
3
  * design/102 (K-8 CC full-body) — TodoWrite parity (CONTEXT leg). A private, in-task plan the model rewrites to
4
4
  * track multi-step work. State lives in this factory's closure — FRESH per task (each `assembleCodeTools` call
5
- * makes its own) and torn down with the task; nothing durable, no deps, no egress. `effect:"idempotent"` because
5
+ * makes its own), one list per calling session (`ctx.sessionId`; upstream keys it by agent, so an Agent child sharing
6
+ * the instance never replaces the leader's plan); nothing durable, no deps, no egress. `effect:"idempotent"` because
6
7
  * re-sending the whole list is repeat-safe. Distinct from `present_plan` (that SUSPENDS for human review); this is
7
- * the model's own scratch. Returns a RAW {@link ToolSpec} pushed via `spec.tools` (prepare-task defines it) — NOT
8
- * mounted default-on, so it stays INERT for non-full-body runs. (A later slice may project the list to the wire.)
8
+ * the model's own scratch, a RAW {@link ToolSpec} pushed via `spec.tools` — NOT mounted default-on (INERT elsewhere).
9
9
  */
10
10
  export declare function createTodoWriteTool(): ToolSpec;
@@ -2,7 +2,7 @@ import { Type } from "typebox";
2
2
  import { errorResult } from "../core/tools.js";
3
3
  import { toolFace } from "../core/tool-catalog-entries.js";
4
4
  export function createTodoWriteTool() {
5
- let todos = [];
5
+ const lists = new Map();
6
6
  return {
7
7
  name: "TodoWrite",
8
8
  ...toolFace("todo-write"),
@@ -81,9 +81,10 @@ export function createTodoWriteTool() {
81
81
  activeForm: Type.String({ minLength: 1, description: "Present-continuous form shown while in progress (e.g. \"Running the tests\")." }),
82
82
  })),
83
83
  }),
84
- execute: (args) => {
84
+ execute: (args, ctx) => {
85
+ const key = ctx?.sessionId ?? "";
85
86
  const shape = (t) => ({ content: t.content, status: t.status, activeForm: t.activeForm });
86
- const oldTodos = todos.map(shape);
87
+ const oldTodos = (lists.get(key) ?? []).map(shape);
87
88
  const written = args.todos;
88
89
  for (let i = 0; i < written.length; i++) {
89
90
  const activeForm = written[i].activeForm;
@@ -96,7 +97,7 @@ export function createTodoWriteTool() {
96
97
  }
97
98
  }
98
99
  const allDone = written.every((t) => t.status === "completed");
99
- todos = allDone ? [] : written;
100
+ lists.set(key, allDone ? [] : written);
100
101
  const newTodos = written.map(shape);
101
102
  const open = written.filter((t) => t.status !== "completed").length;
102
103
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sema-agent/core",
3
- "version": "7.31.0",
3
+ "version": "7.32.0",
4
4
  "description": "Stateless, task-oriented AI agent core",
5
5
  "type": "module",
6
6
  "license": "BUSL-1.1",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_comment": "design/87 L3 — frozen public export surface of src/index.ts (name -> kind). DO NOT edit by hand to silence a red test. A removed/changed entry = a SemVer-BREAKING change; bump MAJOR and update this fixture in the SAME commit (design/87 §4.2 / §5.2). Regenerate via REGEN in test/export-surface.test.ts.",
3
3
  "_tierComment": "#435 v1 — machine-readable layering of the public surface: stable = demonstrated by README.md / src/examples; internal = an `Internal`-marked name or a runner/engine deep-subtree declaration (the model seam src/engine/llm is excluded — it is the BYOM contract, not an engine internal); advanced = a supported export the front door does not walk you through. THIS IS AN INITIAL HEURISTIC, derived mechanically and expected to be refined ticket by ticket: no human reviewed these 1700+ entries one by one, and nothing here claims otherwise. Known bias: a short or English-word export name (ok, err, Result, Usage) can match ordinary prose in README.md and land `stable` on a coincidence. Every export MUST carry a tier — a new export with no row fails the gate in export-surface.test.ts.",
4
- "count": 2478,
4
+ "count": 2484,
5
5
  "exports": {
6
6
  "A2ATaskState": "type",
7
7
  "A2ATaskStateReversal": "type",
@@ -177,10 +177,12 @@
177
177
  "BackgroundAgentStoreError": "class",
178
178
  "BackgroundAgentUsage": "interface",
179
179
  "BackgroundChildEvent": "interface",
180
+ "BackgroundHardKillOutcome": "type",
180
181
  "BackgroundPoll": "interface",
181
182
  "BackgroundShellCapability": "interface",
182
183
  "BackgroundShellError": "class",
183
184
  "BackgroundShellErrorCode": "type",
185
+ "BackgroundShellHardKillReceipt": "interface",
184
186
  "BackgroundShellId": "type",
185
187
  "BackgroundShellStatus": "type",
186
188
  "BackgroundSpawnOptions": "type",
@@ -905,6 +907,7 @@
905
907
  "PERMISSION_MODE_CLASSES": "variable",
906
908
  "PERMISSION_RULE_SYNC_PATH": "variable",
907
909
  "PERMISSION_RULE_WRITER": "variable",
910
+ "PERSISTED_RULE_MANDATES": "variable",
908
911
  "PLAN_SYSTEM_PROMPT": "variable",
909
912
  "PLAN_WHEN_TO_USE": "variable",
910
913
  "PREFIX_INPUT_CLASSIFICATION": "variable",
@@ -1013,6 +1016,7 @@
1013
1016
  "PersistedRuleCoverage": "interface",
1014
1017
  "PersistedRuleHit": "interface",
1015
1018
  "PersistedRuleHitRule": "interface",
1019
+ "PersistedRuleMandate": "type",
1016
1020
  "PersistedRuleMatch": "type",
1017
1021
  "PersistedRuleTool": "type",
1018
1022
  "PersistedRuleUnreadable": "interface",
@@ -1131,6 +1135,7 @@
1131
1135
  "ReadOnlyFlagArity": "type",
1132
1136
  "ReadOnlyShellVerdict": "type",
1133
1137
  "ReadRootGrantCandidate": "interface",
1138
+ "ReadRootGrantScreen": "type",
1134
1139
  "RealApprovalGateBit": "interface",
1135
1140
  "ReasoningFormat": "type",
1136
1141
  "ReasoningIntensity": "type",
@@ -2056,6 +2061,7 @@
2056
2061
  "isPeerSessionId": "function",
2057
2062
  "isPeerSessionProcessAlive": "function",
2058
2063
  "isPermissionModeClass": "function",
2064
+ "isPersistedRuleMandate": "function",
2059
2065
  "isPersonalScope": "function",
2060
2066
  "isProcessLive": "function",
2061
2067
  "isProtectedWrite": "function",
@@ -2657,10 +2663,12 @@
2657
2663
  "BackgroundAgentStoreError": "advanced",
2658
2664
  "BackgroundAgentUsage": "advanced",
2659
2665
  "BackgroundChildEvent": "advanced",
2666
+ "BackgroundHardKillOutcome": "advanced",
2660
2667
  "BackgroundPoll": "advanced",
2661
2668
  "BackgroundShellCapability": "advanced",
2662
2669
  "BackgroundShellError": "advanced",
2663
2670
  "BackgroundShellErrorCode": "advanced",
2671
+ "BackgroundShellHardKillReceipt": "advanced",
2664
2672
  "BackgroundShellId": "advanced",
2665
2673
  "BackgroundShellStatus": "advanced",
2666
2674
  "BackgroundSpawnOptions": "advanced",
@@ -3385,6 +3393,7 @@
3385
3393
  "PERMISSION_MODE_CLASSES": "advanced",
3386
3394
  "PERMISSION_RULE_SYNC_PATH": "advanced",
3387
3395
  "PERMISSION_RULE_WRITER": "advanced",
3396
+ "PERSISTED_RULE_MANDATES": "advanced",
3388
3397
  "PLAN_SYSTEM_PROMPT": "advanced",
3389
3398
  "PLAN_WHEN_TO_USE": "advanced",
3390
3399
  "PREFIX_INPUT_CLASSIFICATION": "advanced",
@@ -3493,6 +3502,7 @@
3493
3502
  "PersistedRuleCoverage": "advanced",
3494
3503
  "PersistedRuleHit": "advanced",
3495
3504
  "PersistedRuleHitRule": "advanced",
3505
+ "PersistedRuleMandate": "advanced",
3496
3506
  "PersistedRuleMatch": "advanced",
3497
3507
  "PersistedRuleTool": "advanced",
3498
3508
  "PersistedRuleUnreadable": "advanced",
@@ -3611,6 +3621,7 @@
3611
3621
  "ReadOnlyFlagArity": "advanced",
3612
3622
  "ReadOnlyShellVerdict": "advanced",
3613
3623
  "ReadRootGrantCandidate": "advanced",
3624
+ "ReadRootGrantScreen": "advanced",
3614
3625
  "RealApprovalGateBit": "advanced",
3615
3626
  "ReasoningFormat": "advanced",
3616
3627
  "ReasoningIntensity": "advanced",
@@ -4536,6 +4547,7 @@
4536
4547
  "isPeerSessionId": "advanced",
4537
4548
  "isPeerSessionProcessAlive": "advanced",
4538
4549
  "isPermissionModeClass": "advanced",
4550
+ "isPersistedRuleMandate": "advanced",
4539
4551
  "isPersonalScope": "advanced",
4540
4552
  "isProcessLive": "advanced",
4541
4553
  "isProtectedWrite": "advanced",
@@ -0,0 +1,189 @@
1
+ {
2
+ "repo": "core",
3
+ "version": "7.31.1",
4
+ "from": "289b2d1998a3e890f6a8659ffc4397074ddd720a",
5
+ "to": "v7.31.1",
6
+ "added": [],
7
+ "removed": [],
8
+ "changed": [],
9
+ "narrowed": [
10
+ {
11
+ "id": "F1",
12
+ "source": "behaviorFlips.summary",
13
+ "text": "NARROWING (#1094, memory store; +1 error code `memory.target_outside_store`, registered in `NON_GOVERNANCE_MEMORY_CODES`; +0 exports, +0 wire keys): an act at a memory store path is judged by where it LANDS, not by how it is spelled. One verdict — the target's DIRECTORY, resolved through every link on its existing prefix, must be the store root's physical form or inside it (the final component is not judged: the backend's `rm` removes a link standing there and `rename` replaces it, the engine's in-place writes refuse it with `ELOOP` — none follows it) — gates `FileMemoryEngineBackend.applyPatches` (per op at staging, the whole batch at the commit point, per op at each act), erasure (`MemoryEngine.eraseMemoryEntries`: commit point and act), journal replay (the whole journal before its first act, each op again at its act), and the engine's own writes at store paths: the copy-out projection, the harvest's missing-file restore, the read-only layer restore and the `MEMORY.md` index writes. The backend takes its root's physical form when it opens the store and re-checks it at the head of every face; the engine takes its memory dir's physical form at its first mount and a later mount that resolves elsewhere is refused. Separately, the engine's in-place write refuses a file with more than one name (a hard link), before anything is truncated. A refused `applyPatches` / erasure throws a plain `Error` whose `.code` is the new code — before the commit marker nothing is written and no journal is left, after it the journal is kept and replays once the path resolves inside again; a refused mount throws the code; a refused restore or index write is not performed and is reported on `HarvestReport.warnings` (or `incident.detail`) with the code, a hard-link refusal names the file. WHO IS HURT: a deployment whose store holds a directory link under its root, or a hard-linked memory file — the act that used to follow it is refused until it is removed. Known limits KL-1094-a … i."
14
+ },
15
+ {
16
+ "id": "F2",
17
+ "source": "behaviorFlips.summary",
18
+ "text": "NARROWING (#1099, spawned-environment scrub; `SecretEnvFindingKind` +1 member `namespace-rule`, confidence `medium`; +0 exports, +0 wire keys): inside the host shell's `SEMA_` namespace, and only there, a credential word as ANY `_`-separated segment makes the name a credential (`SEMA_NAMESPACE_SECRET_RE`, over the one `CREDENTIAL_WORD` alternation the suffix rule reads, the count-word reading of `TOKENS` included) — so `SEMA_MODEL_KEY_<ENTRY>`, `SEMA_WEBSEARCH_KEY_<PROVIDER>` and `SEMA_REGISTRY_TOKEN_FILE` no longer reach a tool's child shell or a language server, and the one `config.secret_env_scrubbed` notice lists them in `detail.names`. The rule is consulted after the two exact-name lists and the suffix rule, so no existing finding changes kind, and every name outside `SEMA_` keeps its v7.31.0 verdict and kind. WHO IS HURT: a `SEMA_` name with a credential-word segment that a child needed (`SEMA_REGISTRY_TOKEN_FILE`, `SEMA_LLM_KEY_DB`, six `SEMA_CODE_*` knobs a nested `sema` reads); an `inheritEnv` allowlist, `shellEnv` / a per-exec env and a host-supplied LSP `env` still pass a name verbatim."
19
+ },
20
+ {
21
+ "id": "F3",
22
+ "source": "behaviorFlips.summary",
23
+ "text": "NARROWING (#1100, shell read boundary; tightening only — no verdict loosens against 7.31.0; +0 exports, +0 wire keys; supersedes #1004's no-opener ruling for tilde values): three rules on one mechanism, the declared-home deny floor. (1) The opener condition leaves the assignment-value reading: an environment value with a leading tilde is placed against the declared home whether or not a program opens it, so `F=~/<deny row>; cat \"$F\"` meets the deny judge exactly as its `$HOME` and literal spellings already did. (2) A LITERAL tilde (quoted, escaped, after an option's `=`) — a relative `./~…` path to bash, but a program such as npm expands it itself — also carries its declared-home spelling as a deny-only view: refused on a deny row, never a containment question. (3) Each member of a `:` list meets the deny rows (a tilde member placed against the home); the whole value is still no containment candidate. Where no deny row is hit nothing changes. WHO IS HURT: a text whose value or literal-tilde operand names a deny row under the declared home and is never read that way is refused, as its `$HOME` and literal spellings already were (KL-1100a / KL-1100b)."
24
+ }
25
+ ],
26
+ "closedSetMembers": [
27
+ {
28
+ "set": "SecretEnvFindingKind",
29
+ "at": [
30
+ "src/core/secret-env.ts:181"
31
+ ],
32
+ "newSet": false,
33
+ "added": [
34
+ "namespace-rule"
35
+ ],
36
+ "removed": [],
37
+ "breaking": false,
38
+ "criteriaCovered": false
39
+ }
40
+ ],
41
+ "closedSetsUnreadable": [],
42
+ "typeKeys": [],
43
+ "typeKeysUnreadableArms": [],
44
+ "typeKeysFlattenedCount": 216,
45
+ "declarationMoves": [],
46
+ "mintMoves": [],
47
+ "criteriaObserves": [
48
+ {
49
+ "kind": "engineNoticeCode",
50
+ "name": "config.secret_env_scrubbed",
51
+ "assertions": [
52
+ "G2.a"
53
+ ]
54
+ },
55
+ {
56
+ "kind": "typeMember",
57
+ "name": "AskRequest.mandated",
58
+ "assertions": [
59
+ "G3.a",
60
+ "G3.b"
61
+ ]
62
+ },
63
+ {
64
+ "kind": "typeMember",
65
+ "name": "EngineNotice.detail",
66
+ "assertions": [
67
+ "G2.a"
68
+ ]
69
+ },
70
+ {
71
+ "kind": "typeMember",
72
+ "name": "FileMemoryEngineBackend.applyPatches",
73
+ "assertions": [
74
+ "G1.a"
75
+ ]
76
+ },
77
+ {
78
+ "kind": "typeMember",
79
+ "name": "HarvestReport.incident",
80
+ "assertions": [
81
+ "G1.a"
82
+ ]
83
+ },
84
+ {
85
+ "kind": "typeMember",
86
+ "name": "HarvestReport.ok",
87
+ "assertions": [
88
+ "G1.b"
89
+ ]
90
+ },
91
+ {
92
+ "kind": "typeMember",
93
+ "name": "HarvestReport.restored",
94
+ "assertions": [
95
+ "G1.a",
96
+ "G1.b"
97
+ ]
98
+ },
99
+ {
100
+ "kind": "typeMember",
101
+ "name": "HarvestReport.warnings",
102
+ "assertions": [
103
+ "G1.a"
104
+ ]
105
+ },
106
+ {
107
+ "kind": "typeMember",
108
+ "name": "MemoryEngine.eraseMemoryEntries",
109
+ "assertions": [
110
+ "G1.a"
111
+ ]
112
+ },
113
+ {
114
+ "kind": "typeMember",
115
+ "name": "MemoryEngine.materialize",
116
+ "assertions": [
117
+ "G1.a"
118
+ ]
119
+ },
120
+ {
121
+ "kind": "typeMember",
122
+ "name": "MemoryErasureAttestation.status",
123
+ "assertions": [
124
+ "G1.b"
125
+ ]
126
+ },
127
+ {
128
+ "kind": "typeMember",
129
+ "name": "PatchReport.applied",
130
+ "assertions": [
131
+ "G1.b"
132
+ ]
133
+ },
134
+ {
135
+ "kind": "typeMember",
136
+ "name": "SecretEnvFinding.confidence",
137
+ "assertions": [
138
+ "G2.a"
139
+ ]
140
+ },
141
+ {
142
+ "kind": "typeMember",
143
+ "name": "SecretEnvFinding.key",
144
+ "assertions": [
145
+ "G2.b"
146
+ ]
147
+ },
148
+ {
149
+ "kind": "typeMember",
150
+ "name": "SecretEnvFinding.kind",
151
+ "assertions": [
152
+ "G2.a",
153
+ "G2.b"
154
+ ]
155
+ },
156
+ {
157
+ "kind": "typeMember",
158
+ "name": "ShellReadVerdict.disposition",
159
+ "assertions": [
160
+ "G3.a",
161
+ "G3.b"
162
+ ]
163
+ },
164
+ {
165
+ "kind": "typeMember",
166
+ "name": "ShellReadVerdict.finding",
167
+ "assertions": [
168
+ "G3.a",
169
+ "G3.b"
170
+ ]
171
+ },
172
+ {
173
+ "kind": "typeMember",
174
+ "name": "ShellReadVerdict.readRootCandidate",
175
+ "assertions": [
176
+ "G3.b"
177
+ ]
178
+ }
179
+ ],
180
+ "consumersHint": {
181
+ "cli": [],
182
+ "server": [],
183
+ "sdk": [],
184
+ "note": "scanned cli@4e593722 server@c60beebe sdk@a9440f2 — a name listed is MENTIONED there today, which is evidence of consumption, not proof of it. Ledger files (**/docs/core-consumption/**, **/docs/wire-consumption/**, **/*LEDGER*.json) are not evidence of use — server: 1 name(s) matched ONLY in bookkeeping files and were set aside (SecretEnvFindingKind)"
185
+ },
186
+ "breakingCount": 0,
187
+ "breakingItems": [],
188
+ "toResolvedAt": "c7c5484e2ab1b4abcba1297de62424e71306e66c"
189
+ }