@sema-agent/core 7.21.1 → 7.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 (110) hide show
  1. package/CHANGELOG.md +290 -1
  2. package/dist/agents/send-message-tool.js +1 -0
  3. package/dist/agents/subagent.d.ts +1 -0
  4. package/dist/agents/subagent.js +2 -1
  5. package/dist/core/ask-origin.d.ts +1 -0
  6. package/dist/core/ask-origin.js +3 -2
  7. package/dist/core/auto-compaction.d.ts +2 -2
  8. package/dist/core/auto-mode-rebuild.js +6 -2
  9. package/dist/core/auto-mode.d.ts +40 -18
  10. package/dist/core/auto-mode.js +63 -45
  11. package/dist/core/checkpoint-store.d.ts +9 -13
  12. package/dist/core/context-edit.js +1 -1
  13. package/dist/core/engine-notice.d.ts +62 -1
  14. package/dist/core/engine-notice.js +14 -1
  15. package/dist/core/gate-bounded-await.d.ts +13 -3
  16. package/dist/core/gate-fold.js +3 -3
  17. package/dist/core/governance-codes.d.ts +1 -1
  18. package/dist/core/governance-codes.js +6 -0
  19. package/dist/core/mcp.d.ts +65 -0
  20. package/dist/core/mcp.js +190 -41
  21. package/dist/core/memory-engine/consolidation.d.ts +5 -0
  22. package/dist/core/memory-engine/consolidation.js +7 -3
  23. package/dist/core/memory-engine/engine.d.ts +8 -0
  24. package/dist/core/memory-engine/engine.js +25 -19
  25. package/dist/core/memory-engine/layout.d.ts +47 -9
  26. package/dist/core/memory-engine/layout.js +157 -57
  27. package/dist/core/memory-engine/session-incarnation.d.ts +27 -0
  28. package/dist/core/memory-engine/session-incarnation.js +63 -0
  29. package/dist/core/parent-spec-seat.d.ts +15 -3
  30. package/dist/core/parent-spec-seat.js +5 -0
  31. package/dist/core/park-selfcheck.js +1 -1
  32. package/dist/core/path-identity.d.ts +11 -4
  33. package/dist/core/path-identity.js +5 -2
  34. package/dist/core/permission-rule-model.d.ts +1 -2
  35. package/dist/core/read-root-candidate.js +11 -1
  36. package/dist/core/retention.d.ts +8 -1
  37. package/dist/core/retention.js +2 -0
  38. package/dist/core/runner/assemble-result.d.ts +2 -7
  39. package/dist/core/runner/assemble-result.js +25 -8
  40. package/dist/core/runner/classifier-ladder.js +1 -0
  41. package/dist/core/runner/contracts.d.ts +26 -15
  42. package/dist/core/runner/inherited-ask-grants.d.ts +2 -7
  43. package/dist/core/runner/inherited-ask-grants.js +1 -0
  44. package/dist/core/runner/mcp-redial.d.ts +109 -0
  45. package/dist/core/runner/mcp-redial.js +60 -0
  46. package/dist/core/runner/memory-consolidation-driver.d.ts +2 -0
  47. package/dist/core/runner/prepare-ask-lane.js +1 -0
  48. package/dist/core/runner/prepare-caps-and-workflow.js +9 -12
  49. package/dist/core/runner/prepare-context-lane.d.ts +43 -1
  50. package/dist/core/runner/prepare-context-lane.js +25 -4
  51. package/dist/core/runner/prepare-hands-readface.d.ts +3 -0
  52. package/dist/core/runner/prepare-hands-readface.js +13 -3
  53. package/dist/core/runner/prepare-memory.js +3 -3
  54. package/dist/core/runner/prepare-protocol-tools.d.ts +6 -1
  55. package/dist/core/runner/prepare-protocol-tools.js +20 -38
  56. package/dist/core/runner/prepare-task.js +4 -4
  57. package/dist/core/runner/prepare-turn-wiring.js +2 -2
  58. package/dist/core/runner/prepare-wiring-manifest.d.ts +4 -0
  59. package/dist/core/runner/prepare-wiring-manifest.js +2 -1
  60. package/dist/core/runner/run-harness-handlers.js +4 -2
  61. package/dist/core/runner/run-identity-wiring.d.ts +3 -1
  62. package/dist/core/runner/run-identity-wiring.js +4 -1
  63. package/dist/core/runner/run-recovery-lanes.js +6 -3
  64. package/dist/core/runner/run-settle-and-teardown.d.ts +3 -1
  65. package/dist/core/runner/run-settle-and-teardown.js +6 -2
  66. package/dist/core/runner/run-telemetry-and-budget-seats.js +11 -1
  67. package/dist/core/runner/run-terminal-adoption.d.ts +4 -1
  68. package/dist/core/runner/run-terminal-adoption.js +2 -6
  69. package/dist/core/runner/runtask.js +8 -5
  70. package/dist/core/runner/stream-lifecycle-verbs.d.ts +5 -1
  71. package/dist/core/runner/stream-lifecycle-verbs.js +23 -2
  72. package/dist/core/runner/write-protection-station.d.ts +7 -25
  73. package/dist/core/runner/write-protection-station.js +18 -0
  74. package/dist/core/runner-deps.d.ts +16 -7
  75. package/dist/core/skill-listing-budget.d.ts +5 -4
  76. package/dist/core/task-registry.d.ts +0 -6
  77. package/dist/core/task-result.d.ts +31 -12
  78. package/dist/core/task-spec.d.ts +2 -1
  79. package/dist/core/task-stream.d.ts +26 -0
  80. package/dist/core/tool-spec.d.ts +5 -1
  81. package/dist/core/trace.d.ts +5 -0
  82. package/dist/core/types.d.ts +3 -3
  83. package/dist/core/types.js +1 -1
  84. package/dist/core/wiring-manifest.d.ts +23 -0
  85. package/dist/core/wiring-manifest.js +3 -0
  86. package/dist/core/write-protect.d.ts +64 -12
  87. package/dist/core/write-protect.js +88 -34
  88. package/dist/engine/execution-env/node-execution-env.d.ts +19 -2
  89. package/dist/engine/execution-env/node-execution-env.js +5 -1
  90. package/dist/index.d.ts +5 -4
  91. package/dist/index.js +2 -2
  92. package/dist/orchestration/workflow-governance.js +1 -1
  93. package/dist/orchestration/workflow-primitives.d.ts +2 -2
  94. package/dist/tools/fs/bash-program-position.d.ts +26 -5
  95. package/dist/tools/fs/bash-program-position.js +63 -16
  96. package/dist/tools/fs/bash-readonly-classifier.d.ts +27 -17
  97. package/dist/tools/fs/bash-readonly-classifier.js +134 -55
  98. package/dist/tools/fs/fs-bash.js +43 -18
  99. package/dist/tools/fs/fs-read.js +2 -1
  100. package/dist/tools/fs/fs-shared.d.ts +33 -7
  101. package/dist/tools/fs/fs-shared.js +12 -1
  102. package/dist/tools/fs/index.d.ts +4 -2
  103. package/dist/tools/fs/read-deny.d.ts +25 -11
  104. package/dist/tools/fs/read-deny.js +5 -1
  105. package/dist/tools/fs/safety.d.ts +38 -4
  106. package/dist/tools/fs/safety.js +31 -3
  107. package/package.json +2 -2
  108. package/test/export-surface.snapshot.json +11 -1
  109. package/wire-changes/core@7.22.0.json +637 -0
  110. package/wire-changes/core@7.23.0.json +316 -0
@@ -30,13 +30,26 @@ export interface WriteProtectedHit {
30
30
  }
31
31
  export declare const WRITE_PROTECTED_DEFAULT_TABLE: readonly WriteProtectedRow[];
32
32
  /**
33
- * The ONE case fold of this module, applied to BOTH sides of every comparison (table names at
34
- * compile, path segments at match) — `toLowerCase` plus the two confusable letters CC's fold maps
35
- * (U+0131 dotless ı → i, U+017F long ſ → s). Deliberately NOT full Unicode confusable folding:
36
- * that would be a different, wider claim than the one made (the read deny set's ASCII-contract doc
37
- * states the same boundary for its own fold).
33
+ * The ONE SEGMENT fold of this module, applied to BOTH sides of every comparison (table names at
34
+ * compile, path segments at match), in the reference implementation's own order:
35
+ * · `toLowerCase` plus the two confusable letters its fold maps (U+0131 dotless ı → i, U+017F long ſ → s);
36
+ * · the INVISIBLE FORMAT characters it strips — the zero-width joiners/non-joiners, the bidirectional
37
+ * overrides and the byte-order mark (U+200C–U+200F, U+202A–U+202E, U+206A–U+206F, U+FEFF), verbatim.
38
+ * A segment carrying one of these LOOKS like the row it is not, on both sides of every comparison, so
39
+ * the strip is a deception axis rather than a platform one: it applies to every reading below, not to
40
+ * the win32 one alone. It can only ADD matches, which on a TIGHTEN costs one extra question — and the
41
+ * data-root passthrough, where an added match would be a guard BYPASSED, deliberately does not use this
42
+ * fold at all (its comparison is byte-exact; see {@link relativeUnderDataRoot}).
43
+ * · …and a strip that would EMPTY the segment keeps the segment instead (the reference fold's own `|| n`):
44
+ * an all-invisible component names a real file, and folding it to "" would make it collide with nothing
45
+ * or with everything depending on the caller.
46
+ * Deliberately NOT full Unicode confusable folding: that would be a different, wider claim than the one made
47
+ * (the read deny set's ASCII-contract doc states the same boundary for its own fold). The NTFS data-stream
48
+ * suffix the reference fold also strips is a win32 NAMESPACE fact, not a comparison one, and lives with the
49
+ * other component normalization of those views ({@link ComponentNormalization}) so a POSIX file legally
50
+ * named `a:b` keeps its own identity on the POSIX reading.
38
51
  */
39
- export declare function foldWriteProtectCase(s: string): string;
52
+ export declare function foldWriteProtectSegment(s: string): string;
40
53
  /**
41
54
  * Resolve the ACTIVE table under the deployment seat (validated, loud — #123's bad-value states all
42
55
  * throw and name the knob). `undefined` = the default table; `[]` = NO table (an explicit, legal
@@ -47,15 +60,32 @@ export declare function foldWriteProtectCase(s: string): string;
47
60
  * candidate configuration with the engine's own rules.
48
61
  */
49
62
  export declare function resolveWriteProtectedTable(entries?: readonly WriteProtectedEntry[]): readonly WriteProtectedRow[];
63
+ /**
64
+ * WHY the ENVIRONMENT half of one judgment could not contribute — the canonicalizer refused the spelling,
65
+ * a link the tree could not follow, a namespace spelling it declines, an environment call that threw.
66
+ * Reported through {@link WriteProtectionCompileOptions.onViewAbsent}; it is a DISCLOSURE, never a verdict
67
+ * (see the absence law on {@link WriteProtectionMatcher.judge}).
68
+ */
69
+ export interface WriteProtectionViewAbsence {
70
+ /** The environment's own sentence, verbatim — what it said when asked to resolve this target. */
71
+ readonly cause: string;
72
+ /** The failure was specifically a symlink whose real target is unknowable, as opposed to a spelling the
73
+ * tree refuses or a probe that errored. */
74
+ readonly unresolvedSymlink: boolean;
75
+ }
50
76
  /**
51
77
  * The judge's answer for ONE covered write ({@link WriteProtectionMatcher.judge}):
52
- * · `clear` — no row on either view: the write is not this layer's business;
78
+ * · `clear` — no row on any view that could answer: the write is not this layer's business;
53
79
  * · `hit` — a row matched; `via` says which view named it: `"spelling"` (the anchored spelling, no
54
80
  * filesystem consulted), `"chain"` (a spelling the leaf symlink chain passes through, judged as a
55
81
  * spelling) or `"target"` (the resolved real target, the spelling being innocent);
56
- * · `unresolvable` — the real target could not be resolved (an unfollowable symlink, an errored probe, a
57
- * namespace spelling the tree refuses); `cause` is the canonicalizer's own sentence and the consumer
58
- * REFUSES, an unjudged target never being vouched.
82
+ * · `unresolvable` — NO half of the judgment answered at all. The judge itself no longer mints this: an
83
+ * environment that cannot resolve the target has ANSWERED, and an answer of "I cannot resolve this"
84
+ * leaves the lexical half's judgment standing (the absence law below). What is left for this verdict is
85
+ * the judgment that never happened — the gate's bounded wait elapsing or the task ending before
86
+ * anything came back (`awaitWriteProtectionJudgment`, gate-bounded-await.ts) — where there is no
87
+ * lexical answer to stand on either, and the consumer REFUSES. `unresolvedSymlink` is that minter's
88
+ * `false`: a wait that produced nothing knows nothing about what it was waiting for.
59
89
  */
60
90
  export type WriteProtectionVerdict = {
61
91
  readonly kind: "clear";
@@ -115,8 +145,8 @@ export interface WriteProtectionMatcher {
115
145
  * @contract write_protection.judges_the_real_target — the always-on write-protection table and the
116
146
  * opt-in sensitive-path deny set differ in VOCABULARY (literal rows / segment globs) and SEVERITY
117
147
  * (ask / deny), never in REACH: both judge the file the tool will write — the spelling anchored at the
118
- * live cwd AND its resolved real target, through one canonicalizer — and a target neither can resolve is
119
- * refused, never vouched. An environment-less judge has no target view and answers by the spelling alone —
148
+ * live cwd AND its resolved real target, through one canonicalizer. Where they DO part: a target the write-protection
149
+ * judge cannot resolve leaves its resolved view ABSENT (the spelling still judges); the deny set still REFUSES whole. An environment-less judge has no target view and answers by the spelling alone —
120
150
  * the same absence law the cwd follows (absent ⇒ the spelling is judged exactly as given). REACH is what
121
151
  * this sentence promises, not a LOCK: the identity is resolved at judgment time and re-resolved by the
122
152
  * tool at execute time (the module header states that window and the seat that would close it).
@@ -136,6 +166,17 @@ export interface WriteProtectionCompileOptions {
136
166
  * path-family source when the call carries no cwd. Omitted ⇒ relative spellings resolve against the
137
167
  * cwd, then the environment's own working directory. */
138
168
  readonly rootPath?: string;
169
+ /**
170
+ * Called at most ONCE per judgment, at the moment the ENVIRONMENT half is found unable to contribute
171
+ * ({@link WriteProtectionViewAbsence}) — before the judgment returns, and on the hit arm as well as the
172
+ * clear one, because the fact is about the ENVIRONMENT rather than about the answer it did not change.
173
+ * Omitted ⇒ the absence is silent, which is the honest default for a judge compiled outside a leg.
174
+ * A throwing sink is the caller's bug and is not caught here; the leg that wires one wraps it.
175
+ *
176
+ * Deliberately carries no call identity: what it reports is a deployment-level fact (this environment
177
+ * cannot answer path questions), and its consumer announces once per leg rather than once per write.
178
+ */
179
+ readonly onViewAbsent?: (absence: WriteProtectionViewAbsence) => void;
139
180
  }
140
181
  /**
141
182
  * Compile the write-protection judge for a deployment configuration. Returns `undefined` when the
@@ -204,5 +245,16 @@ export interface WriteProtectionMount {
204
245
  * environment cannot RESOLVE is the judge's own `unresolvable` verdict, not this null.
205
246
  * Returns `undefined` when the resolved table is empty — nothing to judge, mount nothing.
206
247
  * `opts` are the judge's own seats, passed straight through ({@link compileWriteProtection}).
248
+ *
249
+ * @contract write_protection.membership_is_the_call — WHICH calls the always-on table judges is a
250
+ * PREDICATE OVER THE CALL and nothing else: the call's own face declares a path target whose access is not
251
+ * a read, so a custom writer joins this judge by DECLARING what it writes and a tool that declares nothing
252
+ * is not this layer's business under any name. There is deliberately NO name-list seat beside it, and the
253
+ * asymmetry with the opt-in deny policy — which does take one (`createSensitivePathPolicy({ tools })`, its
254
+ * own pre-declaration hatch) — is the intended shape rather than a gap: a name list on a default-ON floor
255
+ * is a way to narrow that floor silently, one tool name at a time, while the seat this layer gives a
256
+ * deployment for narrowing is the whole-table replacement, which says what it removes. A deployment that
257
+ * wants name-list membership composes the deny policy alongside; the two then answer under the shared
258
+ * `deny > ask > allow` fold, which is what makes the missing seat cost nothing.
207
259
  */
208
260
  export declare function createWriteProtectionCheck(entries?: readonly WriteProtectedEntry[], opts?: WriteProtectionCompileOptions): WriteProtectionMount | undefined;
@@ -57,8 +57,20 @@ export const WRITE_PROTECTED_DEFAULT_TABLE = freezeTable([
57
57
  { name: ".sema.json", kind: "basename" },
58
58
  { name: ".sema", kind: "segment" },
59
59
  ]);
60
- export function foldWriteProtectCase(s) {
61
- return s.toLowerCase().replace(/ı/g, "i").replace(/ſ/g, "s");
60
+ export function foldWriteProtectSegment(s) {
61
+ const cased = s.toLowerCase().replace(/ı/g, "i").replace(/ſ/g, "s");
62
+ return stripInvisibleFormat(cased) || cased;
63
+ }
64
+ function stripInvisibleFormat(s) {
65
+ return s.replace(/[\u200c-\u200f\u202a-\u202e\u206a-\u206f\ufeff]/g, "");
66
+ }
67
+ function stripWin32Component(raw, normalization) {
68
+ const base = normalization.invisibleFirst ? stripInvisibleFormat(raw) : raw;
69
+ const colon = normalization.stream ? base.indexOf(":") : -1;
70
+ return (colon < 0 ? base : base.slice(0, colon)).replace(/[. ]+$/, "");
71
+ }
72
+ function foldWriteProtectName(name) {
73
+ return name.split("/").map(foldWriteProtectSegment).join("/");
62
74
  }
63
75
  const KINDS = ["basename", "segment", "segment-run"];
64
76
  function describeEntryValue(v) {
@@ -107,7 +119,7 @@ export function resolveWriteProtectedTable(entries) {
107
119
  const seen = new Set();
108
120
  for (const entry of entries) {
109
121
  const row = normalizeWriteProtectedEntry(entry);
110
- const key = `${row.kind}:${foldWriteProtectCase(row.name)}`;
122
+ const key = `${row.kind}:${foldWriteProtectName(row.name)}`;
111
123
  if (seen.has(key))
112
124
  continue;
113
125
  seen.add(key);
@@ -115,8 +127,9 @@ export function resolveWriteProtectedTable(entries) {
115
127
  }
116
128
  return freezeTable(out);
117
129
  }
118
- function pathViewSegments(path, win32, seps = BOTH_SEPARATOR_FAMILIES, anchorFloor = 0) {
130
+ function pathViewSegments(path, normalization, seps = BOTH_SEPARATOR_FAMILIES, anchorFloor = 0) {
119
131
  const segs = [];
132
+ const win32 = normalization !== "literal";
120
133
  for (const raw of path.split(seps)) {
121
134
  if (raw.length === 0)
122
135
  continue;
@@ -131,11 +144,17 @@ function pathViewSegments(path, win32, seps = BOTH_SEPARATOR_FAMILIES, anchorFlo
131
144
  segs.push("..");
132
145
  continue;
133
146
  }
134
- const stripped = win32 ? raw.replace(/[. ]+$/, "") : raw;
147
+ const stripped = normalization === "literal" ? raw : stripWin32Component(raw, normalization);
135
148
  segs.push(stripped.length > 0 ? stripped : raw);
136
149
  }
137
150
  return segs;
138
151
  }
152
+ const WIN32_NORMALIZATIONS = [
153
+ { invisibleFirst: false, stream: false },
154
+ { invisibleFirst: false, stream: true },
155
+ { invisibleFirst: true, stream: false },
156
+ { invisibleFirst: true, stream: true },
157
+ ];
139
158
  const BOTH_SEPARATOR_FAMILIES = /[\\/]/;
140
159
  const POSIX_SEPARATOR_FAMILY = /\//;
141
160
  function uncAnchorSegments(path) {
@@ -149,7 +168,7 @@ function relativeUnderDataRoot(path, roots) {
149
168
  if (root.win ? !isWinFormPath(path) : isWinFormPath(path) || !path.startsWith("/"))
150
169
  continue;
151
170
  const seps = root.win ? BOTH_SEPARATOR_FAMILIES : POSIX_SEPARATOR_FAMILY;
152
- const segs = pathViewSegments(path, false, seps, uncAnchorSegments(path));
171
+ const segs = pathViewSegments(path, "literal", seps, uncAnchorSegments(path));
153
172
  if (segs.length < root.segments.length)
154
173
  continue;
155
174
  if (root.segments.every((s, i) => segs[i] === s))
@@ -165,13 +184,13 @@ export function compileWriteProtection(entries, opts) {
165
184
  const dataRootPaths = dataRootSpellings(opts?.dataRoot);
166
185
  const rootViews = dataRootPaths.map((r) => {
167
186
  const win = isWinFormPath(r);
168
- return { win, segments: pathViewSegments(r, false, win ? BOTH_SEPARATOR_FAMILIES : POSIX_SEPARATOR_FAMILY, uncAnchorSegments(r)) };
187
+ return { win, segments: pathViewSegments(r, "literal", win ? BOTH_SEPARATOR_FAMILIES : POSIX_SEPARATOR_FAMILY, uncAnchorSegments(r)) };
169
188
  });
170
189
  const basenames = new Map();
171
190
  const segments = new Map();
172
191
  const runs = [];
173
192
  for (const row of rows) {
174
- const folded = foldWriteProtectCase(row.name);
193
+ const folded = foldWriteProtectName(row.name);
175
194
  if (row.kind === "basename") {
176
195
  if (!basenames.has(folded))
177
196
  basenames.set(folded, row.name);
@@ -187,7 +206,7 @@ export function compileWriteProtection(entries, opts) {
187
206
  const judgeSegments = (raw) => {
188
207
  if (raw.length === 0)
189
208
  return null;
190
- const segs = raw.map(foldWriteProtectCase);
209
+ const segs = raw.map(foldWriteProtectSegment);
191
210
  const base = basenames.get(segs[segs.length - 1] ?? "");
192
211
  if (base !== undefined)
193
212
  return { name: base, kind: "basename" };
@@ -205,9 +224,20 @@ export function compileWriteProtection(entries, opts) {
205
224
  }
206
225
  return null;
207
226
  };
208
- const judgeViews = (spelling) => judgeSegments(pathViewSegments(spelling, false, POSIX_SEPARATOR_FAMILY, uncAnchorSegments(spelling))) ??
209
- judgeSegments(pathViewSegments(spelling, false)) ??
210
- judgeSegments(pathViewSegments(spelling, true));
227
+ const judgeViews = (spelling) => {
228
+ const posix = judgeSegments(pathViewSegments(spelling, "literal", POSIX_SEPARATOR_FAMILY, uncAnchorSegments(spelling)));
229
+ if (posix !== null)
230
+ return posix;
231
+ const bothSeparators = judgeSegments(pathViewSegments(spelling, "literal"));
232
+ if (bothSeparators !== null)
233
+ return bothSeparators;
234
+ for (const normalization of WIN32_NORMALIZATIONS) {
235
+ const hit = judgeSegments(pathViewSegments(spelling, normalization));
236
+ if (hit !== null)
237
+ return hit;
238
+ }
239
+ return null;
240
+ };
211
241
  const judgeSpelled = (spelled) => judgeViews(relativeUnderDataRoot(spelled, rootViews) ?? spelled);
212
242
  const judgeSpelling = (path, cwd) => {
213
243
  const base = typeof cwd === "string" && cwd.length > 0 ? cwd : undefined;
@@ -231,31 +261,55 @@ export function compileWriteProtection(entries, opts) {
231
261
  return { kind: "clear" };
232
262
  const family = pathFamilyOf({ root: rootPath, cwd: ctx.cwd ?? env.cwd });
233
263
  const base = ctx.base ?? ctx.cwd ?? rootPath;
234
- const canon = await canonicalizeTarget(env, path, ctx.signal, family, base);
235
- if (!canon.ok)
236
- return { kind: "unresolvable", cause: canon.message, unresolvedSymlink: canon.unresolvedSymlink === true };
237
- const byTarget = judgeViews(canon.key);
238
- if (byTarget !== null) {
239
- for (const rootKey of await canonicalDataRootKeys(env, dataRootPaths, ctx.signal)) {
240
- const relative = relativeUnderRoot(canon.key, rootKey);
241
- if (relative === null)
264
+ let noted = false;
265
+ const noteAbsence = (cause, unresolvedSymlink) => {
266
+ if (noted)
267
+ return;
268
+ noted = true;
269
+ opts?.onViewAbsent?.({ cause, unresolvedSymlink });
270
+ };
271
+ let canonKey;
272
+ try {
273
+ const canon = await canonicalizeTarget(env, path, ctx.signal, family, base);
274
+ if (canon.ok)
275
+ canonKey = canon.key;
276
+ else
277
+ noteAbsence(canon.message, canon.unresolvedSymlink === true);
278
+ }
279
+ catch (err) {
280
+ noteAbsence(err instanceof Error ? err.message : String(err), false);
281
+ }
282
+ try {
283
+ if (canonKey !== undefined) {
284
+ const byTarget = judgeViews(canonKey);
285
+ if (byTarget !== null) {
286
+ for (const rootKey of await canonicalDataRootKeys(env, dataRootPaths, ctx.signal)) {
287
+ const relative = relativeUnderRoot(canonKey, rootKey);
288
+ if (relative === null)
289
+ continue;
290
+ if (relative === "")
291
+ return { kind: "clear" };
292
+ const remainder = judgeViews(relative);
293
+ return remainder === null ? { kind: "clear" } : { kind: "hit", hit: remainder, via: "target" };
294
+ }
295
+ return { kind: "hit", hit: byTarget, via: "target" };
296
+ }
297
+ }
298
+ for (const hop of await symlinkChainHops(env, path, ctx.signal, family, base, (cause) => noteAbsence(cause, false))) {
299
+ const byHop = judgeSpelled(hop);
300
+ if (byHop === null)
242
301
  continue;
243
- if (relative === "")
244
- return { kind: "clear" };
245
- const remainder = judgeViews(relative);
246
- return remainder === null ? { kind: "clear" } : { kind: "hit", hit: remainder, via: "target" };
302
+ if (canonKey !== undefined) {
303
+ for (const rootKey of await canonicalDataRootKeys(env, dataRootPaths, ctx.signal)) {
304
+ if (relativeUnderRoot(canonKey, rootKey) !== null)
305
+ return { kind: "clear" };
306
+ }
307
+ }
308
+ return { kind: "hit", hit: byHop, via: "chain" };
247
309
  }
248
- return { kind: "hit", hit: byTarget, via: "target" };
249
310
  }
250
- for (const hop of await symlinkChainHops(env, path, ctx.signal, family, base)) {
251
- const byHop = judgeSpelled(hop);
252
- if (byHop === null)
253
- continue;
254
- for (const rootKey of await canonicalDataRootKeys(env, dataRootPaths, ctx.signal)) {
255
- if (relativeUnderRoot(canon.key, rootKey) !== null)
256
- return { kind: "clear" };
257
- }
258
- return { kind: "hit", hit: byHop, via: "chain" };
311
+ catch (err) {
312
+ noteAbsence(err instanceof Error ? err.message : String(err), false);
259
313
  }
260
314
  return { kind: "clear" };
261
315
  },
@@ -7,6 +7,21 @@ import type { SchedulerCapability, ScheduledIntent, SchedulerContext, ScheduledT
7
7
  import { SchedulerError } from "../../core/scheduler.js";
8
8
  /** Convert user-facing timeout seconds into a positive, timer-safe millisecond delay. */
9
9
  export declare function resolveExecTimeoutMs(timeoutSeconds: unknown): number | undefined;
10
+ /**
11
+ * Anchor a RELATIVE symlink target at its link's own directory, UNFOLDED and with exactly one separator
12
+ * between the two. A directory that IS a root already ends in its own separator (`/`, `C:\`, `C:/`,
13
+ * `\\host\share\`, all of which `dirname` answers that way), and concatenating a second one mints a
14
+ * DOUBLE-separator spelling for a path that has one spelling — `/tmp -> private/tmp` came back as
15
+ * `//private/tmp`, which the judges that walk this chain then match against nothing, because a guarded row
16
+ * and a containment root are both written with single separators. Joining through `path.resolve`/`path.join`
17
+ * would fix the separator and fold `..` with it, which is the one thing this walk may not do.
18
+ *
19
+ * `separator` is the one the caller's `dirname` writes, passed rather than read from the module so the rule
20
+ * is a pure function of its inputs. A backslash counts as a terminator only where it IS a separator: on a
21
+ * POSIX tree a directory whose name ends in one (`/a/b\`) is an ordinary name, and reading it as terminated
22
+ * would rename `/a/b\/t` to `/a/b\t` — the same class of rewrite as folding `..` lexically.
23
+ */
24
+ export declare function anchorAtLinkDir(linkDir: string, target: string, separator: string): string;
10
25
  /** review finding (win32): `where bash.exe` can resolve to `System32\bash.exe` — the
11
26
  * WSL LAUNCHER, not a native shell. Running it drops into a Linux subsystem where Windows cwd/paths
12
27
  * do not exist; silently adopting it is exactly the "wrong shell, false green" failure D1 forbids.
@@ -171,8 +186,10 @@ export declare class NodeExecutionEnv implements ExecutionEnv, BackgroundShellCa
171
186
  * target, until a hop is not a link (`EINVAL`), is absent (`ENOENT` / `ENOTDIR`), repeats, or the bound is
172
187
  * reached. A relative target is ANCHORED at its link's directory and never folded — `..` is the filesystem's
173
188
  * to resolve during the next `readlink`'s own lookup (a lexical fold reads `bridge/../next` as `next` and misses
174
- * every link behind `bridge`). One syscall per hop, no `lstat`. On a win32 host a share-shaped hop is kept and
175
- * ends the walk unprobed. Any other errno is an answer nobody can use (`err`), never a shortened chain.
189
+ * every link behind `bridge`). Anchoring puts EXACTLY ONE separator between the directory and the target
190
+ * ({@link anchorAtLinkDir}); a root directory already ends in its own. One syscall per hop, no `lstat`. On a
191
+ * win32 host a share-shaped hop is kept and ends the walk unprobed. Any other errno is an answer nobody can
192
+ * use (`err`), never a shortened chain.
176
193
  */
177
194
  canonicalChain(path: string): Promise<Result<SymlinkChain, FileError>>;
178
195
  exists(path: string): Promise<Result<boolean, FileError>>;
@@ -42,6 +42,10 @@ function fileKindFromStats(stats) {
42
42
  return undefined;
43
43
  }
44
44
  const HAS_POSIX_MODE_FACE = process.platform !== "win32";
45
+ export function anchorAtLinkDir(linkDir, target, separator) {
46
+ const endsInSeparator = linkDir.endsWith(separator) || (separator === "\\" && linkDir.endsWith("/"));
47
+ return endsInSeparator ? `${linkDir}${target}` : `${linkDir}${separator}${target}`;
48
+ }
45
49
  const SYMLINK_CHAIN_HOP_BOUND = 64;
46
50
  function fileInfoFromStats(path, stats) {
47
51
  const kind = fileKindFromStats(stats);
@@ -1043,7 +1047,7 @@ export class NodeExecutionEnv {
1043
1047
  return ok({ hops });
1044
1048
  return err(toFileError(error, cur));
1045
1049
  }
1046
- const next = isAbsolute(target) ? target : `${dirname(cur)}${sep}${target}`;
1050
+ const next = isAbsolute(target) ? target : anchorAtLinkDir(dirname(cur), target, sep);
1047
1051
  if (seen.has(next))
1048
1052
  break;
1049
1053
  hops.push(next);
package/dist/index.d.ts CHANGED
@@ -57,7 +57,7 @@ export { assertOracleIsolation, type GraderEnv, type OracleIsolationVerdict, typ
57
57
  export { decideAutoPromote, deriveTripwire, FROZEN_DENYLIST_FLOOR, type AutoPromoteTerminal, type AutoPromoteInputs, type PromotedRecord, type TripwireResult, } from "./core/auto-promote.js";
58
58
  export { runCascade, type CascadeRung, type CascadeConfig, type CascadeAttempt, type CascadeRunResult, type GateVerdict, } from "./agents/cascade.js";
59
59
  export { pgQuery, mysqlQuery, sqliteQuery } from "./tools/sql-adapters.js";
60
- export { materializeMcpTools, MCP_PREFIX, type MaterializedMcp, type McpServerStatus, type McpRefreshResult } from "./core/mcp.js";
60
+ export { materializeMcpTools, MCP_PREFIX, type MaterializedMcp, type McpServerStatus, type McpRefreshResult, type McpReconnectResult } from "./core/mcp.js";
61
61
  export { MCP_FAILURE_KINDS, MCP_DELIVERY_VERDICTS, classifyMcpFailure, type McpFailure, type McpFailureKind, type McpDelivered } from "./core/mcp-failure.js";
62
62
  export { MCP_INJECTION_DROP_REASONS, MCP_INJECTION_DROP_TEXT, mcpInjectionDroppedNotice, type McpInjectionDropReason, type McpInjectionDropFacts } from "./core/mcp-injection-drop.js";
63
63
  export { askUnresolvableNotice, type AskUnresolvableFacts } from "./core/ask-unresolvable-notice.js";
@@ -70,7 +70,7 @@ export { createSessionRulePolicy } from "./core/runner/session-rule-policy.js";
70
70
  export { LOCKED_KEY_REGISTRY, resolveLockedKeys, type LockedKey, type LockedConfig } from "./core/locked-config.js";
71
71
  export { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, complianceCallDenial, resolveComplianceDenies, type ComplianceCapability, type CompliancePosture, type ComplianceProfile, } from "./core/compliance.js";
72
72
  export { admitMemoryScopes, foldAdmissionFreeze, type OwnOrgAdmissionVerdict, type MemoryAdmissionInput, type MemoryAdmissionOutcome, type MemoryAdmissionVerdict, type MemoryScopeAdmission, type MemoryScopeOrigin, type MemoryScopeRequest, } from "./core/memory-admission.js";
73
- export { assertRetentionCapability, type ManagedRetentionCapability, type RetentionDeclaration, type RetentionDeclaring, type RetentionPolicy, type RetentionReceipt, } from "./core/retention.js";
73
+ export { assertRetentionCapability, RETENTION_CASCADE_DOMAINS, RETENTION_CASCADE_EXEMPT, type ManagedRetentionCapability, type RetentionDeclaration, type RetentionDeclaring, type RetentionPolicy, type RetentionReceipt, } from "./core/retention.js";
74
74
  export { GOVERNANCE_CODES, governanceRetryClass, type GovernanceCode, type GovernanceRetryClass } from "./core/governance-codes.js";
75
75
  export { NOTICE_AUDIENCE, noticeAudienceOf, ENGINE_NOTICE_CODES, type EngineNoticeCode, type NoticeAudience } from "./core/governance-codes.js";
76
76
  export { TtlSessionStore, type TtlSessionStoreOptions, type EvictPolicy } from "./core/session-store.js";
@@ -105,7 +105,7 @@ export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, ty
105
105
  export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
106
106
  export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, type ReadDenyEntry, type ReadDenyMatcher, type NormalizedReadDenyEntry, type ReadDenyBuiltinTier, type ReadDenyBuiltinRow, type ReadDenyBuiltinConfig, } from "./tools/fs/index.js";
107
107
  export { deploymentFaceClampNotice, resolveContainmentFace, type ContainmentFace, type ContainmentFaceInputs, type ContainmentFaceKind, type ContainmentFaceLayer, } from "./tools/fs/index.js";
108
- export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, type WriteProtectedEntry, type WriteProtectedRow, type WriteProtectedKind, type WriteProtectedHit, type WriteProtectionMatcher, type WriteProtectionVerdict, type WriteProtectionJudgeContext, type WriteProtectionCompileOptions, type WriteProtectionCheck, WRITE_PROTECTION_TARGET_VIEWS, type WriteProtectionTargetView, type WriteProtectionMount, } from "./core/write-protect.js";
108
+ export { WRITE_PROTECTED_DEFAULT_TABLE, resolveWriteProtectedTable, compileWriteProtection, type WriteProtectedEntry, type WriteProtectedRow, type WriteProtectedKind, type WriteProtectedHit, type WriteProtectionMatcher, type WriteProtectionVerdict, type WriteProtectionViewAbsence, type WriteProtectionJudgeContext, type WriteProtectionCompileOptions, type WriteProtectionCheck, WRITE_PROTECTION_TARGET_VIEWS, type WriteProtectionTargetView, type WriteProtectionMount, } from "./core/write-protect.js";
109
109
  export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, toolResultContentSegment, MAX_MINTED_TOOL_RESULT_REF_CHARS, type ToolResultProvenance, assertToolResultProvenanceMatch, normalizeToolResultProvenance, toolResultProvenanceOf, ToolResultRefConflictError, TOOL_RESULT_REF_CONFLICT_CODE, type ToolResultStore, type ToolResultSlice, type ToolResultDeletionReport, } from "./core/tool-result-store.js";
110
110
  export { InMemoryCheckpointStore, CheckpointError, mintCheckpointId, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_CHECKPOINT_VERSION, REAL_APPROVAL_CHECKPOINT_VERSION, RESOURCE_CHECKPOINT_VERSION, TOKEN_CHECKPOINT_VERSION, debitLedger, remainingBudgetMicroUsd, remainingTokens, winnerFromOutcome, validatePendingSteer, readPendingSteerQueue, appendPendingSteer, MAX_PENDING_STEER_CHARS, MAX_PENDING_STEER_ENTRIES, PENDING_STEER_QUEUE_BYTE_BUDGET_BYTES, PENDING_STEER_FROZEN_FIELDS, ACTOR_ASSERTION_FROZEN_FIELDS, MAX_ACTOR_FIELD_CHARS, MAX_STEER_INPUT_ID_CHARS, type ActorAssertion, type PendingSteerEntry, type PendingSteerInput, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, type ProbeCause, type ProbeCauseOperands, type CheckpointStore, type CheckpointSummary, type Checkpoint, type CheckpointToken, type CheckpointGate, type CheckpointState, type SerializedCheckpointState, type CheckpointFaultMode, type PendingAction, type ResumeOutcome, type ResolvedOutcome, type ReopenReason, type ResolveExpectation, type TerminalClaimIntent, type TerminalClaimOutcome, type SafetyAxis, type RealApprovalGateBit, type ResourceLedger, type ResourceLimitReason, type PlatformLimitReason, type CheckpointRow, realApprovalOrgFact, resolveCheckpointStore, checkpointStoreExecutionUndeclared, } from "./core/checkpoint-store.js";
111
111
  export { EXECUTION_OUTCOME_RECORD_WORDS, EXECUTION_RECORD_LEAVES_ROW_UNRECORDED, type ExecutionOutcomeRecordWord, type ExecutionRecordRowFacts, type ExecutionVerdict, ExecutionOutcomeConflictError, checkpointExecutionRecorded, executionRecordDisposition, sameExecutionRecord, executionVerdict, } from "./core/checkpoint-execution-record.js";
@@ -225,7 +225,8 @@ export { termSet, jaccardDistance, cosineDistance } from "./core/memory-vector.j
225
225
  export { encodeSurfacedKey, buildManifestText, validateSelectedIds, composeSelectiveBody, formatMemoryAge, resolveLinkedIds, RECALL_CAVEAT, DEFAULT_MAX_SELECTED, DEFAULT_MAX_LINKED, type MemorySelector, type MemorySelectRequest, type SelectiveRecallOptions, type SelectiveRecallResult, type LayeredRecallOptions, type LayeredRecallResult, } from "./core/memory-recall.js";
226
226
  export { runMemoryConsolidation, CONSOLIDATION_SYSTEM_PROMPT, DEFAULT_CONSOLIDATION_BAND, DEFAULT_CONSOLIDATION_SEARCH_LIMIT, DEFAULT_CONSOLIDATION_MAX_NOTES, normalizeForExactMatch, type ConsolidationParams, type ConsolidationStats, type ConsolidationNote, type ConsolidationLLM, } from "./core/runner/memory-consolidation.js";
227
227
  export { consolidateScope, advanceCursorAfterInline, type ConsolidateScopeDeps, type ConsolidateScopeOptions, } from "./core/consolidate-scope.js";
228
- export { resolveMemoryConsolidationDriver, type MemoryConsolidationDriverDeps, } from "./core/runner/memory-consolidation-driver.js";
228
+ export { resolveMemoryConsolidationDriver, autoRunOnRecommendationEffective, // the ONE arming reading (`memory.auto_consolidation.arming_has_one_resolver`) — the seat's absence is not "off"
229
+ type MemoryConsolidationDriverDeps, } from "./core/runner/memory-consolidation-driver.js";
229
230
  export { DEFAULT_COMPACTION_INSTRUCTIONS } from "./core/auto-compaction.js";
230
231
  export { DEFAULT_SYSTEM_PROMPT, CODE_AGENT_PROMPT, SUBAGENT_PROMPT, CODE_SYSTEM_PROMPT, MEMORY_GUIDANCE, MEMORY_SAFETY, MEMORY_HYGIENE, NO_PERSISTENT_MEMORY_NOTICE, OUTPUT_EFFICIENCY, CYBER_RISK, HARNESS_SECTION_ANCHOR, URL_SAFETY, SUMMARIZE_TOOL_RESULTS, EXECUTION_ENVIRONMENT, EXECUTION_ENVIRONMENT_OPEN_READS, executionEnvironmentBlockFor, harnessContext, buildEnvironmentContext, buildGitSnapshot, buildScratchpadSection, GIT_STATUS_MAX_CHARS, formatLocalDate, type EnvironmentFacts, defaultPromptProvider, composeSystemPrompt, composeConstitution, constitutionBlocks, type PromptBlock, analyzePromptCacheFriendliness, assertPromptCacheFriendly, type PromptProvider, type StablePromptContext, type PromptCacheReport, type PromptTextDeclaration, } from "./prompts/default.js";
231
232
  export { SUPERVISOR_PROMPT, ORCHESTRATION_GUIDANCE, ORCHESTRATION_AWARENESS, GOAL_COMPLETION_GUIDANCE } from "./prompts/supervisor.js";
package/dist/index.js CHANGED
@@ -56,7 +56,7 @@ export { createSessionRulePolicy } from "./core/runner/session-rule-policy.js";
56
56
  export { LOCKED_KEY_REGISTRY, resolveLockedKeys } from "./core/locked-config.js";
57
57
  export { BUILTIN_COMPLIANCE_DENIES, COMPLIANCE_CAPABILITIES, WEB_FETCH_TOOL_NAME, complianceCallDenial, resolveComplianceDenies, } from "./core/compliance.js";
58
58
  export { admitMemoryScopes, foldAdmissionFreeze, } from "./core/memory-admission.js";
59
- export { assertRetentionCapability, } from "./core/retention.js";
59
+ export { assertRetentionCapability, RETENTION_CASCADE_DOMAINS, RETENTION_CASCADE_EXEMPT, } from "./core/retention.js";
60
60
  export { GOVERNANCE_CODES, governanceRetryClass } from "./core/governance-codes.js";
61
61
  export { NOTICE_AUDIENCE, noticeAudienceOf, ENGINE_NOTICE_CODES } from "./core/governance-codes.js";
62
62
  export { TtlSessionStore } from "./core/session-store.js";
@@ -183,7 +183,7 @@ export { termSet, jaccardDistance, cosineDistance } from "./core/memory-vector.j
183
183
  export { encodeSurfacedKey, buildManifestText, validateSelectedIds, composeSelectiveBody, formatMemoryAge, resolveLinkedIds, RECALL_CAVEAT, DEFAULT_MAX_SELECTED, DEFAULT_MAX_LINKED, } from "./core/memory-recall.js";
184
184
  export { runMemoryConsolidation, CONSOLIDATION_SYSTEM_PROMPT, DEFAULT_CONSOLIDATION_BAND, DEFAULT_CONSOLIDATION_SEARCH_LIMIT, DEFAULT_CONSOLIDATION_MAX_NOTES, normalizeForExactMatch, } from "./core/runner/memory-consolidation.js";
185
185
  export { consolidateScope, advanceCursorAfterInline, } from "./core/consolidate-scope.js";
186
- export { resolveMemoryConsolidationDriver, } from "./core/runner/memory-consolidation-driver.js";
186
+ export { resolveMemoryConsolidationDriver, autoRunOnRecommendationEffective, } from "./core/runner/memory-consolidation-driver.js";
187
187
  export { DEFAULT_COMPACTION_INSTRUCTIONS } from "./core/auto-compaction.js";
188
188
  export { DEFAULT_SYSTEM_PROMPT, CODE_AGENT_PROMPT, SUBAGENT_PROMPT, CODE_SYSTEM_PROMPT, MEMORY_GUIDANCE, MEMORY_SAFETY, MEMORY_HYGIENE, NO_PERSISTENT_MEMORY_NOTICE, OUTPUT_EFFICIENCY, CYBER_RISK, HARNESS_SECTION_ANCHOR, URL_SAFETY, SUMMARIZE_TOOL_RESULTS, EXECUTION_ENVIRONMENT, EXECUTION_ENVIRONMENT_OPEN_READS, executionEnvironmentBlockFor, harnessContext, buildEnvironmentContext, buildGitSnapshot, buildScratchpadSection, GIT_STATUS_MAX_CHARS, formatLocalDate, defaultPromptProvider, composeSystemPrompt, composeConstitution, constitutionBlocks, analyzePromptCacheFriendliness, assertPromptCacheFriendly, } from "./prompts/default.js";
189
189
  export { SUPERVISOR_PROMPT, ORCHESTRATION_GUIDANCE, ORCHESTRATION_AWARENESS, GOAL_COMPLETION_GUIDANCE } from "./prompts/supervisor.js";
@@ -297,7 +297,7 @@ function pickWhitelist(scriptSpec) {
297
297
  if (!Array.isArray(readDenyPatternsRaw)) {
298
298
  throw new WorkflowScriptError('agent(spec): `readDenyPatterns` must be an array of deny entries (a "/"-separated segment run such as ".ssh", or { pattern, caseSensitive? })');
299
299
  }
300
- const entries = mapUntrustedArray(readDenyPatternsRaw, "agent(spec): `readDenyPatterns`", MAX_SCRIPT_DENY_ENTRIES, "Every entry is judged against every path the child reads, for the child's whole run; the built-in and deployment entries are always in force on top of these.", pickReadDenyEntry);
300
+ const entries = mapUntrustedArray(readDenyPatternsRaw, "agent(spec): `readDenyPatterns`", MAX_SCRIPT_DENY_ENTRIES, "Every entry is judged against every path the child reads, for the child's whole run; the deployment's own entries, and whichever built-in tiers the deployment switched on (none by default), are in force on top of these.", pickReadDenyEntry);
301
301
  try {
302
302
  compileReadDeny(entries, "agent(spec).readDenyPatterns");
303
303
  }
@@ -51,8 +51,8 @@ parentReadFace?: () => TaskSpec["readFace"],
51
51
  /** The WRITE seat's twin of the above — the host run's RESOLVED write-face containment, folded
52
52
  * stricter-wins into every spawned child on its own seat (the two seats never read each other). */
53
53
  parentWriteFace?: () => TaskSpec["writeFace"],
54
- /** Twin of the above for the deny-set additions (built-ins always apply; these are the extra
55
- * entries the host's own deployment/task layers stacked on). */
54
+ /** Twin of the above for the deny-set additions (the extra entries the host's own deployment/task
55
+ * layers stacked on; the built-in rows the DEPLOYMENT switched on — none by default — apply beside them). */
56
56
  parentReadDenyPatterns?: () => TaskSpec["readDenyPatterns"],
57
57
  /** #345 ③ — the HOST task's write-hands clamp is ON (`spec.handsReadOnly === true`). Injected
58
58
  * tighten-only into every spawned child, like the read-face clamp above; see the injection
@@ -47,9 +47,16 @@ export declare const ARGV_REENTRY_PROGRAM_RE: RegExp;
47
47
  export declare const ARGV_REENTRY_DEFAULT_PROGRAM = "echo";
48
48
  /**
49
49
  * What a launcher declares about its OWN options: each exact spelling with its ARITY — `0` for a switch, `1` for an
50
- * option whose value is the next token. A fused `--name=value` and every spelling not in the map fall to the refusal
51
- * (the conservative side, and the verdict those spellings always had); a `1` with no token left to take is not
52
- * readable either.
50
+ * option whose value is the next token. Every spelling not in the map falls to the refusal (the conservative side,
51
+ * and the verdict those spellings always had); a `1` with no token left to take is not readable either.
52
+ *
53
+ * THREE SPELLINGS OF ONE DECLARATION, because getopt has three: an arity-1 entry is honoured separated (`-k 5`),
54
+ * fused to its long name (`--signal=TERM`) and fused to its short letter (`-k5`), and an arity-0 short entry inside
55
+ * a cluster of arity-0 letters (`-pv`). Reading only the exact spelling made one program two.
56
+ *
57
+ * WHAT A ROW MAY NOT GET WRONG: declaring an arity-1 option as arity 0 steps onto its VALUE and reads the value as
58
+ * the program — the one direction that turns a refusal into a wrong answer rather than a lost one. A row carries
59
+ * only options whose arity every supported implementation agrees on; a flag no row declares still refuses.
53
60
  */
54
61
  export interface LauncherRow {
55
62
  /** Consulted ONLY for the launcher the command line itself names — a bare keyword in front of the command
@@ -58,6 +65,19 @@ export interface LauncherRow {
58
65
  * them would turn a whole refusal into an ordinary walked segment — a widening, where every other read of this
59
66
  * table only ever finds MORE program to judge. */
60
67
  readonly optionArity: ReadonlyMap<string, 0 | 1>;
68
+ /** A bare NEGATIVE integer in this launcher's option section is its OWN argument (`nice -5 <cmd>`), not a flag
69
+ * the table cannot read. Per row: elsewhere a dash-leading token is an option, and reading one as a number
70
+ * would step over a flag whose meaning this table does not know. */
71
+ readonly negativeIntegerIsOwnArgument?: true;
72
+ /** Positional operands the launcher takes BEFORE the program, consumed whatever they spell (`timeout DURATION
73
+ * COMMAND`); a spelling-based reading under-accepted, and every spelling it missed became the program word. */
74
+ readonly requiredOperands?: number;
75
+ /** The launcher reads NAME=VALUE operands with ITS OWN name grammar (`env 1=x …`), wider than the shell's. */
76
+ readonly assignmentOperands?: true;
77
+ /** The launcher hands its operands to a SHELL as text (`watch "cat /x"` runs `sh -c`): no program word to read. */
78
+ readonly shellText?: true;
79
+ /** With no program word the launcher runs a SHELL: piped text would reach it, so an empty program position refuses. */
80
+ readonly whenNoProgram?: "shell";
61
81
  }
62
82
  /**
63
83
  * The ONE launcher truth: programs that RUN ANOTHER PROGRAM named in ordinary argv tokens, so a declared reader can
@@ -136,7 +156,8 @@ export interface ProgramPosition {
136
156
  */
137
157
  export declare function readProgramPosition(toks: SegmentTokens): ProgramPosition;
138
158
  /** Does this program position hand a program operands the walk cannot see? True when the chain re-enters through
139
- * stdin AND the program it runs is not declared no-path. ONE reading, shared by every consumer. */
140
- export declare function argvReentryUnjudged(pos: ProgramPosition, noPathPrograms: ReadonlySet<string>): boolean;
159
+ * stdin AND the program it runs is not one the caller accepts as a stream target. ONE reading, shared by every
160
+ * consumer; WHICH targets are accepted is the caller's declaration, not this reader's. */
161
+ export declare function argvReentryUnjudged(pos: ProgramPosition, acceptedTargets: ReadonlySet<string>): boolean;
141
162
  /** ONE sentence per refusal kind, minted here and nowhere else. */
142
163
  export declare function programPositionRefusalSentence(refusal: ProgramPositionRefusal): string;