@sema-agent/core 5.23.0 → 5.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +192 -1
- package/dist/core/checkpoint-store.d.ts +84 -10
- package/dist/core/checkpoint-store.js +3 -1
- package/dist/core/hooks.d.ts +18 -5
- package/dist/core/hooks.js +28 -4
- package/dist/core/memory-engine/engine.js +18 -3
- package/dist/core/permission-rule-org.d.ts +9 -0
- package/dist/core/permission-rule-org.js +12 -5
- package/dist/core/permission-rule-store.d.ts +25 -14
- package/dist/core/permission-rule-store.js +5 -1
- package/dist/core/runner/prepare-task.d.ts +2 -0
- package/dist/core/runner/prepare-task.js +40 -9
- package/dist/core/runner/runtask.js +49 -6
- package/dist/core/runner/session-file-state-replay.js +3 -0
- package/dist/core/tool-policy.d.ts +11 -0
- package/dist/core/tool-policy.js +17 -15
- package/dist/core/tool-result-store.d.ts +8 -0
- package/dist/core/tool-result-store.js +77 -3
- package/dist/core/types.d.ts +7 -5
- package/dist/index.d.ts +13 -7
- package/dist/index.js +2 -2
- package/dist/stores/file/adoption/adopt.d.ts +23 -3
- package/dist/stores/file/adoption/adopt.js +4 -8
- package/dist/stores/file/adoption/marker.d.ts +41 -18
- package/dist/stores/file/adoption/marker.js +14 -7
- package/dist/stores/file/permission-rule-store.d.ts +15 -1
- package/dist/stores/file/permission-rule-store.js +4 -1
- package/dist/stores/file/session-policy-store.d.ts +11 -1
- package/dist/stores/file/session-policy-store.js +8 -3
- package/dist/stores/file/task-list-store.d.ts +15 -1
- package/dist/stores/file/task-list-store.js +2 -2
- package/dist/tools/fs/fs-bash.js +7 -4
- package/dist/tools/fs/fs-shared.js +23 -7
- package/dist/tools/monitor.js +3 -3
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -88,7 +88,7 @@ export { HAND_TOOL_EFFECTS, bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW,
|
|
|
88
88
|
export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, type BashReadonlyRootBoundary, type CompoundReadonlyVerdict, } from "./tools/fs/index.js";
|
|
89
89
|
export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
|
|
90
90
|
export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, type ToolResultStore, type ToolResultSlice, } from "./core/tool-result-store.js";
|
|
91
|
-
export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_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, LEGACY_PENDING_STEER_INPUT_ID, type ActorAssertion, type PendingSteerEntry, type PendingSteerInput, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, 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 SafetyAxis, type ResourceLedger, type ResourceLimitReason, type PlatformLimitReason, } from "./core/checkpoint-store.js";
|
|
91
|
+
export { InMemoryCheckpointStore, CheckpointError, 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, LEGACY_PENDING_STEER_INPUT_ID, type ActorAssertion, type PendingSteerEntry, type PendingSteerInput, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, type RiskDescriptor, 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 SafetyAxis, type RealApprovalGateBit, type ResourceLedger, type ResourceLimitReason, type PlatformLimitReason, } from "./core/checkpoint-store.js";
|
|
92
92
|
export { InMemoryUsageWindowStore, GLOBAL_USAGE_KEY, EMPTY_USAGE_WINDOW_RECORD, chargeUsageRecord, readUsageRecord, usageRetryAfterMs, resolveUsageWindows, type UsageWindow, type UsageWindowStore, type UsageWindowReading, type UsageWindowRecord, type UsageSlot, type UsageBucketRow, } from "./core/usage-window-store.js";
|
|
93
93
|
export { FileUsageWindowStore } from "./stores/file/usage-window-store.js";
|
|
94
94
|
export { ENV_LIFETIME_SUSPEND_MARGIN_MS, USAGE_WINDOW_REAP_MARGIN_MS } from "./core/runner/prepare-task.js";
|
|
@@ -136,14 +136,20 @@ export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRul
|
|
|
136
136
|
/**
|
|
137
137
|
* design/179 — persisted ALLOW rules: the standing form of approvals a person already gave.
|
|
138
138
|
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
* and
|
|
139
|
+
* The consent boundary, restated precisely (ruled 2026-08-10): the ENGINE's only write
|
|
140
|
+
* path into a rule store is the consent protocol below (a durable approval record, an authenticated
|
|
141
|
+
* confirmation, a principal-bound redemption). That fact is a property of the engine's wiring, not of
|
|
142
|
+
* what this file exports — a deployment always owned its own storage bytes. The BACKEND CONTRACT
|
|
143
|
+
* (writer types, the writer handle key, and the fold pure functions) is therefore exported further
|
|
144
|
+
* down, so an out-of-repo store implementation (the server's SQL twin) is built against the same
|
|
145
|
+
* definitions as the file backend instead of mirroring them — mirrored types are how comment/contract
|
|
146
|
+
* drift starts. What stays deliberately absent is any convenience that mints rules AROUND consent:
|
|
147
|
+
* exporting the contract gives a store implementer the same powers it already had over its own bytes,
|
|
148
|
+
* and nothing more. Removal is exported without ceremony, because narrowing on a user's behalf is
|
|
149
|
+
* allowed and widening is not.
|
|
144
150
|
*/
|
|
145
151
|
export { parseAllowRuleText, formatAllowRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, type PersistedAllowRule, type RuleTombstone, type RuleScope, type RuleDot, type RuleAdd, type RuleAddOrigin, type RuleSuggestion, type RuleReject, type RuleRejectCode, type ParsedAllowRule, type PersistedRuleTool, type PersistedRuleMatch, } from "./core/permission-rule-model.js";
|
|
146
|
-
export { removePersistedRule, applyTombstones, sameScope, InMemoryPermissionRuleStore, EMPTY_RULE_STORE, type PermissionRuleStore, type PermissionRuleStoreProvider, type StoredAllowRules, type RemoveResult, type PutResult, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, type RuleSyncState, type RuleSyncFrontier, type RuleSyncDrop, type RuleSyncLandingReport, type RuleOwner, type QuarantinedRuleAdd, } from "./core/permission-rule-store.js";
|
|
152
|
+
export { removePersistedRule, applyTombstones, sameScope, InMemoryPermissionRuleStore, EMPTY_RULE_STORE, type PermissionRuleStore, type PermissionRuleStoreProvider, type StoredAllowRules, type RemoveResult, type PutResult, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, type RuleSyncState, type RuleSyncFrontier, type RuleSyncDrop, type RuleSyncLandingReport, type RuleOwner, type QuarantinedRuleAdd, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, type PermissionRuleWriter, type WritablePermissionRuleStore, type RuleWriteDelta, type RuleAddDelta, type RuleDeleteDelta, type RuleSyncJoinDelta, type RawRuleSyncState, type RedemptionAuthorization, } from "./core/permission-rule-store.js";
|
|
147
153
|
export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, type PermissionRuleSyncTransport, type PermissionRuleSyncResult, type RuleSyncRequestBody, type RuleSyncResponseBody, } from "./core/permission-rule-sync.js";
|
|
148
154
|
export { createOrgRuleOverlay, orgRuleVerdictFor, effectivePermissionRules, orgRuleStatePersistenceOf, ORG_UNAVAILABLE_DECISION_REASON, ORG_RULE_DECISION_REASON, ORG_ADJUDICATION_TIMEOUT_MS, type OrgPermissionRule, type OrgRuleSnapshot, type OrgRuleSnapshotProvider, type OrgRuleStatePersistence, type PersistedOrgRuleState, type OrgRuleOverlay, type OrgOverlayResolution, type OrgOverlayStatus, type EffectivePermissionRule, } from "./core/permission-rule-org.js";
|
|
149
155
|
export { RULE_SYNC_DROP_CODES, type RuleSyncDropReason, type RuleQuarantineReason } from "./core/governance-codes.js";
|
package/dist/index.js
CHANGED
|
@@ -69,7 +69,7 @@ export { HAND_TOOL_EFFECTS, bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW,
|
|
|
69
69
|
export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, } from "./tools/fs/index.js";
|
|
70
70
|
export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
|
|
71
71
|
export { InMemoryToolResultStore, OFFLOAD_TOOL_NAME, DEFAULT_TOOL_RESULT_THRESHOLD_CHARS, assertSafeToolResultRef, buildToolResultRef, } from "./core/tool-result-store.js";
|
|
72
|
-
export { InMemoryCheckpointStore, CheckpointError, mintCheckpointToken, checkpointVersionOf, CURRENT_CHECKPOINT_VERSION, MAX_SUPPORTED_CHECKPOINT_VERSION, ORG_ADMISSION_CHECKPOINT_VERSION, F012_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, LEGACY_PENDING_STEER_INPUT_ID, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, } from "./core/checkpoint-store.js";
|
|
72
|
+
export { InMemoryCheckpointStore, CheckpointError, 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, LEGACY_PENDING_STEER_INPUT_ID, riskSeverity, buildRiskDescriptor, summarizeCheckpoint, } from "./core/checkpoint-store.js";
|
|
73
73
|
export { InMemoryUsageWindowStore, GLOBAL_USAGE_KEY, EMPTY_USAGE_WINDOW_RECORD, chargeUsageRecord, readUsageRecord, usageRetryAfterMs, resolveUsageWindows, } from "./core/usage-window-store.js";
|
|
74
74
|
export { FileUsageWindowStore } from "./stores/file/usage-window-store.js";
|
|
75
75
|
export { ENV_LIFETIME_SUSPEND_MARGIN_MS, USAGE_WINDOW_REAP_MARGIN_MS } from "./core/runner/prepare-task.js";
|
|
@@ -112,7 +112,7 @@ export { buildAutoModePrompt, renderAutoModeWindow, renderAutoModeAction, AUTO_M
|
|
|
112
112
|
export { AUTO_MODE_BASE_PROMPT, AUTO_MODE_PERMISSIONS_EXTERNAL } from "./core/auto-mode-prompt-assets.js";
|
|
113
113
|
export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRule, wildcardMatch, } from "./core/permission-rules.js";
|
|
114
114
|
export { parseAllowRuleText, formatAllowRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, } from "./core/permission-rule-model.js";
|
|
115
|
-
export { removePersistedRule, applyTombstones, sameScope, InMemoryPermissionRuleStore, EMPTY_RULE_STORE, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, } from "./core/permission-rule-store.js";
|
|
115
|
+
export { removePersistedRule, applyTombstones, sameScope, InMemoryPermissionRuleStore, EMPTY_RULE_STORE, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, } from "./core/permission-rule-store.js";
|
|
116
116
|
export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, } from "./core/permission-rule-sync.js";
|
|
117
117
|
export { createOrgRuleOverlay, orgRuleVerdictFor, effectivePermissionRules, orgRuleStatePersistenceOf, ORG_UNAVAILABLE_DECISION_REASON, ORG_RULE_DECISION_REASON, ORG_ADJUDICATION_TIMEOUT_MS, } from "./core/permission-rule-org.js";
|
|
118
118
|
export { RULE_SYNC_DROP_CODES } from "./core/governance-codes.js";
|
|
@@ -24,9 +24,29 @@
|
|
|
24
24
|
* ⑥ the marker is atomically rewritten into its PERMANENT terminal record carrying the immutable
|
|
25
25
|
* report. Never deleted; a re-run short-circuits to it.
|
|
26
26
|
*
|
|
27
|
-
* FREEZE (design/183 §8 load-bearing premise): from the moment ② lands, the
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* FREEZE (design/183 §8 load-bearing premise): from the moment ② lands, the root is closed to new
|
|
28
|
+
* writers — and it is worth being exact about which mechanism covers what, because the two halves
|
|
29
|
+
* meet at a seam rather than overlapping (backlog #136⑧):
|
|
30
|
+
* · the LOCKS cover the boot-locked shape: this machine holds `LOCK` (the engine's own boot lock,
|
|
31
|
+
* taken by `FileStorageBackend`) and `ADOPTION-LOCK` for the whole arc, so a live backend refuses
|
|
32
|
+
* the adoption instead of interleaving with it, and a second adoption refuses too;
|
|
33
|
+
* · invariant I6 (`assertAdoptionBootGate`) covers CONSTRUCTION over a DATA-ROOT ANCHOR: while the
|
|
34
|
+
* marker is in flight, a store constructor handed the data root refuses it — including across a
|
|
35
|
+
* crash, when no lock survives.
|
|
36
|
+
* What NEITHER covers, enumerated rather than implied:
|
|
37
|
+
* · an ALREADY-constructed instance over this root through a path that does not hold `LOCK` (the
|
|
38
|
+
* independent store faces — `new FileMemoryStore(root)`, the mailbox family). It keeps its open
|
|
39
|
+
* handles, and I6 is a construction-time gate, not a write-time one;
|
|
40
|
+
* · a NEW construction whose anchor is not the data root. Two faces take a directory that need not
|
|
41
|
+
* BE the data root — `FilePermissionRuleStoreProvider` (the rule bucket) and
|
|
42
|
+
* `createFileTaskListStore` (the document's own directory) — so each takes its root anchor as an
|
|
43
|
+
* explicit option, and a caller that omits it constructs unGATED during a freeze. That default is
|
|
44
|
+
* what this arc's own nested rule leg rides (it must reach the bucket while the marker is in
|
|
45
|
+
* flight); a deployment wanting the gate passes the anchor. Both arms are pinned.
|
|
46
|
+
* That is the same boundary the file family draws everywhere else (one live writer per data root is
|
|
47
|
+
* the deployment's contract, mechanically enforced only for the backend-factory shape), and it is why
|
|
48
|
+
* "stop the engine before adopting" remains an operational instruction and not merely a machine-
|
|
49
|
+
* checked one.
|
|
30
50
|
* Idempotent resume ("re-run completes the unfinished legs") is only sound because no per-store row
|
|
31
51
|
* moves underneath it; the freeze is the premise of idempotence, not an implementation detail.
|
|
32
52
|
*
|
|
@@ -249,6 +249,7 @@ const ZERO_ACTION_STORES = [
|
|
|
249
249
|
"tool-result",
|
|
250
250
|
"file-snapshot",
|
|
251
251
|
"memory",
|
|
252
|
+
"memory-engine",
|
|
252
253
|
"task-list",
|
|
253
254
|
"mailbox",
|
|
254
255
|
"workflow-run",
|
|
@@ -500,8 +501,8 @@ export async function adoptLocalDataRoot(opts) {
|
|
|
500
501
|
};
|
|
501
502
|
}
|
|
502
503
|
}
|
|
503
|
-
if (
|
|
504
|
-
const rows = rewriteSessionPolicyRows(opts.root, opts.from.principal, opts.toPrincipal);
|
|
504
|
+
if (legBits[SESSION_POLICY_LEG]?.done !== true) {
|
|
505
|
+
const rows = rewriteSessionPolicyRows(opts.root, opts.from.kind === "principal" ? opts.from.principal : null, opts.toPrincipal);
|
|
505
506
|
legBits = { ...legBits, [SESSION_POLICY_LEG]: { done: true, rows } };
|
|
506
507
|
publishMarker(opts.root, adoptionId, opts.from, opts.toPrincipal, 2, plan, legBits);
|
|
507
508
|
}
|
|
@@ -557,12 +558,7 @@ export async function adoptLocalDataRoot(opts) {
|
|
|
557
558
|
}
|
|
558
559
|
if (plan.rulesDir !== undefined)
|
|
559
560
|
legs.push({ store: "permission-rule", action: "bucket-rebind" });
|
|
560
|
-
|
|
561
|
-
legs.push({ store: SESSION_POLICY_LEG, action: "row-rewrite", rows: legBits[SESSION_POLICY_LEG]?.rows ?? 0 });
|
|
562
|
-
}
|
|
563
|
-
else {
|
|
564
|
-
legs.push({ store: SESSION_POLICY_LEG, action: "none" });
|
|
565
|
-
}
|
|
561
|
+
legs.push({ store: SESSION_POLICY_LEG, action: "row-rewrite", rows: legBits[SESSION_POLICY_LEG]?.rows ?? 0 });
|
|
566
562
|
const carried = Object.entries(legBits)
|
|
567
563
|
.filter(([k]) => k.startsWith(CARRIAGE_BIT_PREFIX))
|
|
568
564
|
.map(([k, bit]) => ({
|
|
@@ -5,8 +5,11 @@
|
|
|
5
5
|
* Two shapes live in `<dataRoot>/adoption.json`:
|
|
6
6
|
* - the IN-FLIGHT marker (`marker`): a monotonically-advanced `phase` plus per-leg completion bits
|
|
7
7
|
* for the one leg class without a self-attesting completion predicate (row rewrites). While this
|
|
8
|
-
* shape is on disk the root is FROZEN: every file store constructor
|
|
9
|
-
* ({@link assertAdoptionBootGate} — invariant I6)
|
|
8
|
+
* shape is on disk the root is FROZEN: every file store constructor ANCHORED TO THE DATA ROOT
|
|
9
|
+
* refuses to build over it ({@link assertAdoptionBootGate} — invariant I6). Coverage is stated
|
|
10
|
+
* precisely at the gate's own docstring: two faces whose constructor argument is not the root
|
|
11
|
+
* take an optional `dataRoot` anchor, and a caller omitting it is outside the gate (the seam is
|
|
12
|
+
* documented and pinned where those faces live).
|
|
10
13
|
* - the PERMANENT terminal record (`adopted`): never deleted. It is the idempotence anchor (a re-run
|
|
11
14
|
* with the same (from → toPrincipal) short-circuits to the recorded receipt) and it carries the
|
|
12
15
|
* IMMUTABLE report — the byte-stable half of every receipt.
|
|
@@ -153,18 +156,26 @@ export type RootAdoptionFile = {
|
|
|
153
156
|
export declare const ROOT_ADOPTION_FILE = "adoption.json";
|
|
154
157
|
/**
|
|
155
158
|
* design/183 §6 (r4) — the CLOSED SET of by-design-not-migrated assets, verbatim from the design's
|
|
156
|
-
* written list.
|
|
157
|
-
*
|
|
158
|
-
*
|
|
159
|
+
* written list. The WRITER emits exactly this list and the drift pin (§10.5 ① — the design-vs-code
|
|
160
|
+
* two-cell guard) lives in the TEST GRID, where a ruled growth of the set is a deliberate edit.
|
|
161
|
+
*
|
|
162
|
+
* #133: the terminal READ validator deliberately does NOT compare a stored report against this
|
|
163
|
+
* constant. A persisted report is a snapshot of the set AS RULED AT ADOPTION TIME; the set is
|
|
164
|
+
* allowed to grow by ruling (§10.5 ① names that road), so an exact-equality read check would mark
|
|
165
|
+
* every already-adopted root corrupt on the release after any growth (and again on rollback) — 13
|
|
166
|
+
* construction sites all refusing to serve. The read validator checks SHAPE and integrity
|
|
167
|
+
* (non-empty, well-formed entries, no duplicate assets — what an empty/mangled account rebuild
|
|
168
|
+
* actually needs), never version-crossed content equality.
|
|
159
169
|
*/
|
|
160
170
|
export declare const NOT_MIGRATED_BY_DESIGN: ReadonlyArray<{
|
|
161
171
|
asset: string;
|
|
162
172
|
ruling: string;
|
|
163
173
|
}>;
|
|
164
|
-
/** The five baseline obligation IDENTITIES (design/183 §6 minimum face).
|
|
165
|
-
*
|
|
166
|
-
*
|
|
167
|
-
*
|
|
174
|
+
/** The five baseline obligation IDENTITIES (design/183 §6 minimum face). Since #133 the terminal READ
|
|
175
|
+
* validator deliberately does NOT compare a stored report against this table (the stored report is a
|
|
176
|
+
* snapshot of the set as ruled at adoption time; see the validator's own note) — the enforcement that
|
|
177
|
+
* the WRITER emits exactly this set lives in the test grid (test/file-adoption-root.test.ts pins the
|
|
178
|
+
* fresh report's identities against this constant), where a drift is a deliberate, reviewable edit. */
|
|
168
179
|
export declare const BASELINE_CONFIG_IDENTITIES: ReadonlyArray<{
|
|
169
180
|
deployment: string;
|
|
170
181
|
key: string;
|
|
@@ -178,17 +189,29 @@ export declare function readRootAdoptionFile(root: string): RootAdoptionFile | u
|
|
|
178
189
|
/** Atomically publish the root marker (write-temp → fsync → rename, same discipline as every store). */
|
|
179
190
|
export declare function writeRootAdoptionFile(root: string, content: RootAdoptionFile): void;
|
|
180
191
|
/**
|
|
181
|
-
* Invariant I6 (design/183 §3.1) — the adoption BOOT GATE
|
|
182
|
-
*
|
|
183
|
-
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
192
|
+
* Invariant I6 (design/183 §3.1) — the adoption BOOT GATE: an in-flight root marker means the root is
|
|
193
|
+
* mid-adoption (possibly after a crash), and serving a half-migrated root is refused LOUDLY. The
|
|
194
|
+
* refusal names the in-flight arc and the way forward (resume `adoptLocalDataRoot` to completion). A
|
|
195
|
+
* terminal record passes: a completed adoption is a normal, readable root. A corrupt marker throws
|
|
196
|
+
* (fail-closed, see {@link readRootAdoptionFile}).
|
|
197
|
+
*
|
|
198
|
+
* WHO calls it, precisely: every store constructor whose argument IS the data root (that is most of
|
|
199
|
+
* the family — they derive their own subdirectory from it). The two faces whose argument is a
|
|
200
|
+
* caller-chosen directory instead — `FilePermissionRuleStoreProvider` (a rule bucket) and
|
|
201
|
+
* `createFileTaskListStore` (the document's directory) — cannot anchor on their argument, so each
|
|
202
|
+
* takes the data root as an explicit option and is gated only when given one. `root` here is always
|
|
203
|
+
* the ANCHOR, never "wherever this store keeps its bytes": passing a subdirectory reads a marker that
|
|
204
|
+
* is not there and passes unconditionally.
|
|
186
205
|
*
|
|
187
206
|
* This gate is what turns "the engine must be stopped during adoption" from an operational assumption
|
|
188
207
|
* into a machine-checked invariant across crashes: the adoption's own locks die with its process, but
|
|
189
|
-
* the marker (and this gate) survive. Boundary, stated honestly: the gate fires at CONSTRUCTION time
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
208
|
+
* the marker (and this gate) survive. Boundary, stated honestly: the gate fires at CONSTRUCTION time,
|
|
209
|
+
* and it is the ONLY thing this function does — it never reaches an object that already exists. So a
|
|
210
|
+
* store instance constructed BEFORE the marker landed keeps writing for as long as it is held, in
|
|
211
|
+
* ANOTHER OS process (cross-process sharing of one data dir is the file family's documented
|
|
212
|
+
* UNSUPPORTED shape — task-list F-14, mailbox RB-249) and, symmetrically, in THIS one (an independent
|
|
213
|
+
* store face built before the adoption started; the arc's `LOCK` acquisition excludes a live
|
|
214
|
+
* `FileStorageBackend`, which is the only shape that takes that lock). Adoption adds no new promise on
|
|
215
|
+
* either side of that seam — see the freeze note on `adoptLocalDataRoot` for the full division.
|
|
193
216
|
*/
|
|
194
217
|
export declare function assertAdoptionBootGate(root: string, storeName: string): void;
|
|
@@ -74,7 +74,7 @@ function isRootAdoptionFileShape(v) {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
const legs = (m.legs ?? {});
|
|
77
|
-
if (m.phase >= 3 &&
|
|
77
|
+
if (m.phase >= 3 && legs["session-policy"]?.done !== true)
|
|
78
78
|
return false;
|
|
79
79
|
if (m.phase === 5) {
|
|
80
80
|
for (const store of p.carriage) {
|
|
@@ -123,14 +123,21 @@ function isRootAdoptionFileShape(v) {
|
|
|
123
123
|
return false;
|
|
124
124
|
configIds.add(id);
|
|
125
125
|
return true;
|
|
126
|
-
})
|
|
127
|
-
BASELINE_CONFIG_IDENTITIES.every((b) => configIds.has(JSON.stringify([b.deployment, b.key])));
|
|
126
|
+
});
|
|
128
127
|
const nmd = r.notMigratedByDesign;
|
|
128
|
+
const nmdAssets = new Set();
|
|
129
129
|
const closedSetOk = Array.isArray(nmd) &&
|
|
130
|
-
nmd.length
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
nmd.length > 0 &&
|
|
131
|
+
nmd.every((e) => {
|
|
132
|
+
if (e === null || typeof e !== "object")
|
|
133
|
+
return false;
|
|
134
|
+
const m = e;
|
|
135
|
+
if (typeof m.asset !== "string" || m.asset === "" || typeof m.ruling !== "string" || m.ruling === "")
|
|
136
|
+
return false;
|
|
137
|
+
if (nmdAssets.has(m.asset))
|
|
138
|
+
return false;
|
|
139
|
+
nmdAssets.add(m.asset);
|
|
140
|
+
return true;
|
|
134
141
|
});
|
|
135
142
|
const legsOk = Array.isArray(r.legs) &&
|
|
136
143
|
r.legs.every((l) => {
|
|
@@ -157,12 +157,26 @@ declare class FilePermissionRuleStore implements WritablePermissionRuleStore {
|
|
|
157
157
|
*
|
|
158
158
|
* An absent principal resolves to a store that reads zero rules and refuses to write — an unauthenticated
|
|
159
159
|
* task does not share anyone's bucket.
|
|
160
|
+
*
|
|
161
|
+
* ROOT FREEZE (design/183 invariant I6, backlog #136⑤): every sibling file store gates construction on
|
|
162
|
+
* the DATA ROOT's adoption marker, but this provider is built over the RULE BUCKET directory, which is
|
|
163
|
+
* a caller-chosen path that need not sit under (or equal) the data root — so `dir` is not a usable
|
|
164
|
+
* anchor for that gate and the bucket was writable straight through a root freeze. The anchor is
|
|
165
|
+
* therefore taken EXPLICITLY, via `opts.dataRoot`: supply it whenever the rule bucket belongs to a data
|
|
166
|
+
* root that `adoptLocalDataRoot` may adopt, and construction is refused while that root is mid-adoption
|
|
167
|
+
* exactly like `FileStorageBackend`'s. Omitting it keeps the previous behavior (no root gate) — which is
|
|
168
|
+
* also the EXEMPTION the adoption machine itself rides: its nested design/182 rule leg constructs this
|
|
169
|
+
* provider without a `dataRoot` precisely because it runs while the marker is in flight, and the
|
|
170
|
+
* bucket-level marker (read in `forPrincipal`/`forLocalOwner`) is what governs writes inside that arc.
|
|
160
171
|
*/
|
|
161
172
|
export declare class FilePermissionRuleStoreProvider implements PermissionRuleStoreProvider {
|
|
162
173
|
private readonly dir;
|
|
163
174
|
private readonly onError?;
|
|
164
175
|
private lock;
|
|
165
|
-
constructor(dir: string, onError?: ((message: string) => void) | undefined
|
|
176
|
+
constructor(dir: string, onError?: ((message: string) => void) | undefined, opts?: {
|
|
177
|
+
/** The data root whose adoption marker gates construction (design/183 I6). Absent ⇒ no root gate. */
|
|
178
|
+
dataRoot?: string;
|
|
179
|
+
});
|
|
166
180
|
/** Take the writer lock, once, on first use of a write face. Reading never calls this. */
|
|
167
181
|
private acquireWriteLock;
|
|
168
182
|
forPrincipal(principal: string | undefined): PermissionRuleStore;
|
|
@@ -4,6 +4,7 @@ import { createHash, randomBytes } from "node:crypto";
|
|
|
4
4
|
import { PERMISSION_RULE_WRITER, applySyncJoin, applyTombstones, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, foldDelta, } from "../../core/permission-rule-store.js";
|
|
5
5
|
import { canonicalize } from "../../core/canonical-json.js";
|
|
6
6
|
import { BootLock } from "./fs-atomic.js";
|
|
7
|
+
import { assertAdoptionBootGate } from "./adoption/marker.js";
|
|
7
8
|
const LOCAL_OWNER_FILE = "local-owner.json";
|
|
8
9
|
const ADOPTION_FILE = "PERMISSION-RULES-ADOPTION.json";
|
|
9
10
|
export function readRuleAdoptionFile(dir) {
|
|
@@ -298,9 +299,11 @@ export class FilePermissionRuleStoreProvider {
|
|
|
298
299
|
dir;
|
|
299
300
|
onError;
|
|
300
301
|
lock;
|
|
301
|
-
constructor(dir, onError) {
|
|
302
|
+
constructor(dir, onError, opts) {
|
|
302
303
|
this.dir = dir;
|
|
303
304
|
this.onError = onError;
|
|
305
|
+
if (opts?.dataRoot !== undefined)
|
|
306
|
+
assertAdoptionBootGate(opts.dataRoot, "FilePermissionRuleStoreProvider");
|
|
304
307
|
}
|
|
305
308
|
acquireWriteLock = () => {
|
|
306
309
|
if (this.lock !== undefined)
|
|
@@ -31,6 +31,15 @@ export interface FileSessionPolicyStoreOptions {
|
|
|
31
31
|
export declare class FileSessionPolicyStore implements SessionPolicyStore {
|
|
32
32
|
private readonly dir;
|
|
33
33
|
private readonly onCorruptRead;
|
|
34
|
+
/** #132 — the ADOPTED identity of a local-owner root's anonymous lane, or undefined on an
|
|
35
|
+
* unadopted (or principal-adopted) root. A local-owner adoption rebinds the anonymous
|
|
36
|
+
* `[sid,null]` estate rows to `toPrincipal`; until the deployment's principal wiring lands
|
|
37
|
+
* (`REQUIRE_PRINCIPAL` et al. — recorded `migrated:false` in the config account, an operator
|
|
38
|
+
* action), the runtime still queries anonymously, and ENOENT there would silently drop the very
|
|
39
|
+
* tighten-only deny rules the rebind moved. Adoption is a transfer of the whole anonymous
|
|
40
|
+
* identity, so on such a root the anonymous key IS the adopted principal — reads and writes
|
|
41
|
+
* alike (a window-era anonymous write must not mint a fresh orphan row). */
|
|
42
|
+
private readonly anonymousAlias;
|
|
34
43
|
constructor(root: string, opts?: FileSessionPolicyStoreOptions);
|
|
35
44
|
/** The one delivery point for {@link onCorruptRead}. Swallow-guarded here so no caller has to remember. */
|
|
36
45
|
private disclose;
|
|
@@ -41,7 +50,8 @@ export declare class FileSessionPolicyStore implements SessionPolicyStore {
|
|
|
41
50
|
* (swallow-guarded; never on a plain ENOENT, which really is absence). */
|
|
42
51
|
private discloseCorrupt;
|
|
43
52
|
/** `(sessionId, principal)` → a safe, INJECTIVE filename (sanitizeScope appends the full sha256 of the raw
|
|
44
|
-
* composite key, so distinct keys never collide on disk).
|
|
53
|
+
* composite key, so distinct keys never collide on disk). On a local-owner-adopted root the
|
|
54
|
+
* anonymous lane resolves to the adopted principal's key (#132 — see {@link anonymousAlias}). */
|
|
45
55
|
private pathFor;
|
|
46
56
|
private read;
|
|
47
57
|
getRules(sessionId: string, principal?: string): Promise<StoredSessionRules | null>;
|
|
@@ -2,15 +2,19 @@ import { readFileSync, readdirSync, unlinkSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { loosenReasons, normalizeRules, stripRev, SessionPolicyError, } from "../../core/session-policy-store.js";
|
|
4
4
|
import { atomicWriteFile, ensureDir, sanitizeScope } from "./fs-atomic.js";
|
|
5
|
-
import { assertAdoptionBootGate } from "./adoption/marker.js";
|
|
5
|
+
import { assertAdoptionBootGate, readRootAdoptionFile } from "./adoption/marker.js";
|
|
6
6
|
export class FileSessionPolicyStore {
|
|
7
7
|
dir;
|
|
8
8
|
onCorruptRead;
|
|
9
|
+
anonymousAlias;
|
|
9
10
|
constructor(root, opts) {
|
|
10
11
|
assertAdoptionBootGate(root, "FileSessionPolicyStore");
|
|
11
12
|
this.dir = join(root, "session-policy");
|
|
12
13
|
ensureDir(this.dir);
|
|
13
14
|
this.onCorruptRead = opts?.onCorruptRead;
|
|
15
|
+
const adoption = readRootAdoptionFile(root);
|
|
16
|
+
this.anonymousAlias =
|
|
17
|
+
adoption !== undefined && "adopted" in adoption && adoption.adopted.from.kind === "local-owner" ? adoption.adopted.toPrincipal : undefined;
|
|
14
18
|
}
|
|
15
19
|
disclose(info) {
|
|
16
20
|
try {
|
|
@@ -23,7 +27,8 @@ export class FileSessionPolicyStore {
|
|
|
23
27
|
this.disclose({ sessionId, ...(principal !== undefined ? { principal } : {}), path: this.pathFor(sessionId, principal), reason });
|
|
24
28
|
}
|
|
25
29
|
pathFor(sessionId, principal) {
|
|
26
|
-
const
|
|
30
|
+
const effective = principal ?? this.anonymousAlias;
|
|
31
|
+
const composite = JSON.stringify([sessionId, effective ?? null]);
|
|
27
32
|
return join(this.dir, `${sanitizeScope(composite)}.json`);
|
|
28
33
|
}
|
|
29
34
|
read(sessionId, principal) {
|
|
@@ -78,7 +83,7 @@ export class FileSessionPolicyStore {
|
|
|
78
83
|
}
|
|
79
84
|
}
|
|
80
85
|
const next = { ...clean, rev: (prior?.rev ?? 0) + 1 };
|
|
81
|
-
atomicWriteFile(join(this.dir, "tmp"), this.pathFor(sessionId, principal), JSON.stringify({ ...next, __sid: sessionId, __principal: principal ?? null }));
|
|
86
|
+
atomicWriteFile(join(this.dir, "tmp"), this.pathFor(sessionId, principal), JSON.stringify({ ...next, __sid: sessionId, __principal: (principal ?? this.anonymousAlias) ?? null }));
|
|
82
87
|
return next;
|
|
83
88
|
}
|
|
84
89
|
async listBySession(sessionId) {
|
|
@@ -1,2 +1,16 @@
|
|
|
1
1
|
import type { TaskListStore } from "../../tools/task-list.js";
|
|
2
|
-
export declare function createFileTaskListStore(root: string
|
|
2
|
+
export declare function createFileTaskListStore(root: string, opts?: {
|
|
3
|
+
/**
|
|
4
|
+
* The DATA ROOT whose adoption marker gates construction (design/183 I6). Most of the file family
|
|
5
|
+
* takes the data root and derives `join(root, "<family>")` itself; this factory is one of the TWO
|
|
6
|
+
* faces that instead take a caller-chosen directory (the other being
|
|
7
|
+
* `FilePermissionRuleStoreProvider`, which takes a rule bucket) — here, the document's own
|
|
8
|
+
* directory. So when a deployment hands in a SUBDIRECTORY the I6 check would look for the marker
|
|
9
|
+
* inside that subdirectory, find nothing, and pass unconditionally: a gate that is always open
|
|
10
|
+
* (backlog #136⑦). Supply the data root here and the gate reads the anchor the root-taking faces
|
|
11
|
+
* read. Absent ⇒ the directory argument doubles as the anchor, which is exact when it IS the data
|
|
12
|
+
* root (the conventional wiring) and is the previous behavior otherwise; the adoption freeze note
|
|
13
|
+
* on `adoptLocalDataRoot` lists that default among the seams it does not cover.
|
|
14
|
+
*/
|
|
15
|
+
dataRoot?: string;
|
|
16
|
+
}): TaskListStore;
|
|
@@ -4,9 +4,9 @@ import { assertJsonMetadata, normalizeTaskShape } from "../../tools/task-list.js
|
|
|
4
4
|
import { canonicalStoreKey, atomicWriteFile, ensureDir } from "./fs-atomic.js";
|
|
5
5
|
import { assertAdoptionBootGate } from "./adoption/marker.js";
|
|
6
6
|
const dirLocks = new Map();
|
|
7
|
-
export function createFileTaskListStore(root) {
|
|
7
|
+
export function createFileTaskListStore(root, opts) {
|
|
8
8
|
const dir = resolve(root);
|
|
9
|
-
assertAdoptionBootGate(dir, "createFileTaskListStore");
|
|
9
|
+
assertAdoptionBootGate(opts?.dataRoot !== undefined ? resolve(opts.dataRoot) : dir, "createFileTaskListStore");
|
|
10
10
|
const tmpDir = join(dir, "tmp");
|
|
11
11
|
const path = join(dir, "task-list.json");
|
|
12
12
|
ensureDir(dir);
|
package/dist/tools/fs/fs-bash.js
CHANGED
|
@@ -14,9 +14,12 @@ import { BASH_READONLY_DEFAULT_ALLOW, coarseReadonlyCheck, classifyBoundedReadon
|
|
|
14
14
|
export function bashReversibilityProbe(allow, boundary) {
|
|
15
15
|
const allowSet = new Set(allow ?? BASH_READONLY_DEFAULT_ALLOW);
|
|
16
16
|
return (args) => {
|
|
17
|
-
const
|
|
17
|
+
const a = args;
|
|
18
|
+
const command = a?.command;
|
|
18
19
|
if (typeof command !== "string")
|
|
19
20
|
return { reversible: false };
|
|
21
|
+
if (a?.run_in_background === true)
|
|
22
|
+
return { reversible: false };
|
|
20
23
|
const resolved = typeof boundary === "function" ? boundary() : boundary;
|
|
21
24
|
if (classifyCompoundReadonly(command, allowSet, resolved) === undefined)
|
|
22
25
|
return { reversible: true };
|
|
@@ -555,8 +558,8 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
|
|
|
555
558
|
const bgCappedByMax = appliedTimeoutSec !== undefined && requestedTimeoutSec !== undefined && typeof bgCaps.maxBgTimeoutSec === "number" && requestedTimeoutSec > bgCaps.maxBgTimeoutSec;
|
|
556
559
|
const budgetNote = appliedTimeoutSec !== undefined
|
|
557
560
|
? bgCappedByMax
|
|
558
|
-
? ` Time budget: requested ${requestedTimeoutSec}s, capped at ${bgCaps.maxBgTimeoutSec}s (env ceiling: requests above ${bgCaps.maxBgTimeoutSec}s are reduced to it) —
|
|
559
|
-
: ` Time budget:
|
|
561
|
+
? ` Time budget: requested ${requestedTimeoutSec}s, capped at ${bgCaps.maxBgTimeoutSec}s (env ceiling: requests above ${bgCaps.maxBgTimeoutSec}s are reduced to it) — the process may run up to ${appliedTimeoutSec}s.`
|
|
562
|
+
: ` Time budget: the process may run up to ${appliedTimeoutSec}s (hard cap ${bgCaps.maxBgTimeoutSec}s).`
|
|
560
563
|
: "";
|
|
561
564
|
const lifetimeNote = backgroundLifetimeNote(bgCaps, bgSessionScoped, bgEnvIsolatedOwned);
|
|
562
565
|
const registry = taskOpts.taskRegistry;
|
|
@@ -655,7 +658,7 @@ export function createBashTool(env, rootCanonical, coAuthor = false, cwdRef = {
|
|
|
655
658
|
const tail = stdoutSoFar.slice(-1_000);
|
|
656
659
|
const adoptCaps = detachEnv.backgroundCapabilities;
|
|
657
660
|
const adoptBudgetNote = typeof adoptCaps.defaultBgTimeoutSec === "number" && typeof adoptCaps.maxBgTimeoutSec === "number"
|
|
658
|
-
? ` Time budget:
|
|
661
|
+
? ` Time budget: the process may run up to ${Math.min(adoptCaps.defaultBgTimeoutSec, adoptCaps.maxBgTimeoutSec)}s (hard cap ${adoptCaps.maxBgTimeoutSec}s).`
|
|
659
662
|
: "";
|
|
660
663
|
return {
|
|
661
664
|
content: (cause === "timeout"
|
|
@@ -89,12 +89,19 @@ export function envErrorDetail(message) {
|
|
|
89
89
|
}
|
|
90
90
|
const announcedTimeoutConfig = new Set();
|
|
91
91
|
function announceDiscardedTimeout(knob, raw, usedMs) {
|
|
92
|
-
if (
|
|
93
|
-
return;
|
|
94
|
-
const defect = bashTimeoutDefect(Math.floor(raw));
|
|
95
|
-
if (defect === undefined)
|
|
92
|
+
if (typeof raw === "string" && raw === "")
|
|
96
93
|
return;
|
|
97
|
-
const
|
|
94
|
+
const n = typeof raw === "number" ? raw : Number(raw);
|
|
95
|
+
let line;
|
|
96
|
+
if (!Number.isFinite(n) || n <= 0) {
|
|
97
|
+
line = `${knob}=${String(raw)} was ignored — not a positive finite millisecond count. Using ${usedMs}ms instead.`;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const defect = bashTimeoutDefect(Math.floor(n));
|
|
101
|
+
if (defect === undefined)
|
|
102
|
+
return;
|
|
103
|
+
line = `${knob}=${String(raw)} was ignored — it ${defect}. Using ${usedMs}ms instead.`;
|
|
104
|
+
}
|
|
98
105
|
if (announcedTimeoutConfig.has(line))
|
|
99
106
|
return;
|
|
100
107
|
announcedTimeoutConfig.add(line);
|
|
@@ -126,11 +133,20 @@ export function resolveBashTimeoutCaps(opts) {
|
|
|
126
133
|
if (opts?.bashDefaultTimeoutMs !== undefined)
|
|
127
134
|
announceDiscardedTimeout("bashDefaultTimeoutMs", opts.bashDefaultTimeoutMs, defaultMs);
|
|
128
135
|
else if (process.env.BASH_DEFAULT_TIMEOUT_MS !== undefined)
|
|
129
|
-
announceDiscardedTimeout("BASH_DEFAULT_TIMEOUT_MS",
|
|
136
|
+
announceDiscardedTimeout("BASH_DEFAULT_TIMEOUT_MS", process.env.BASH_DEFAULT_TIMEOUT_MS, defaultMs);
|
|
130
137
|
if (opts?.bashMaxTimeoutMs !== undefined)
|
|
131
138
|
announceDiscardedTimeout("bashMaxTimeoutMs", opts.bashMaxTimeoutMs, maxMs);
|
|
132
139
|
else if (process.env.BASH_MAX_TIMEOUT_MS !== undefined)
|
|
133
|
-
announceDiscardedTimeout("BASH_MAX_TIMEOUT_MS",
|
|
140
|
+
announceDiscardedTimeout("BASH_MAX_TIMEOUT_MS", process.env.BASH_MAX_TIMEOUT_MS, maxMs);
|
|
141
|
+
const requestedCap = validTimeoutMs(opts?.bashMaxTimeoutMs) ?? validTimeoutMs(Number(process.env.BASH_MAX_TIMEOUT_MS));
|
|
142
|
+
if (requestedCap !== undefined && requestedCap < defaultMs) {
|
|
143
|
+
const knob = opts?.bashMaxTimeoutMs !== undefined ? "bashMaxTimeoutMs" : "BASH_MAX_TIMEOUT_MS";
|
|
144
|
+
const line = `${knob}=${requestedCap} is below the resolved default budget (${defaultMs}ms) — the ceiling was raised to ${maxMs}ms (the default always fits under the cap).`;
|
|
145
|
+
if (!announcedTimeoutConfig.has(line)) {
|
|
146
|
+
announcedTimeoutConfig.add(line);
|
|
147
|
+
console.warn(line);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
134
150
|
return { defaultMs, maxMs };
|
|
135
151
|
}
|
|
136
152
|
export function __resetBashTimeoutAnnouncements() {
|
package/dist/tools/monitor.js
CHANGED
|
@@ -121,7 +121,7 @@ export function createMonitorTool(env, opts) {
|
|
|
121
121
|
const envWallMs = bgTimeout !== undefined ? bgTimeout * 1000 : undefined;
|
|
122
122
|
const envWallWins = !isPersistent && envWallMs !== undefined && envWallMs < timeoutMs;
|
|
123
123
|
const persistentBudget = bgTimeout !== undefined
|
|
124
|
-
? `the execution environment
|
|
124
|
+
? `the execution environment's background time budget ends the watch after ${bgTimeout}s`
|
|
125
125
|
: `the execution environment's background time budget still applies`;
|
|
126
126
|
const retainedByEnv = env.backgroundCapabilities.retainBackgroundProcesses === true;
|
|
127
127
|
const retainedBySpec = opts.retainBackgroundProcesses === true;
|
|
@@ -142,8 +142,8 @@ export function createMonitorTool(env, opts) {
|
|
|
142
142
|
const lifetime = isPersistent
|
|
143
143
|
? `It is persistent: no watch timeout of its own${timeoutIgnoredNote}, but ${persistentBudget}${persistentAnchor}`
|
|
144
144
|
: envWallWins
|
|
145
|
-
? `
|
|
146
|
-
: `
|
|
145
|
+
? `The watch may run up to ${envWallMs}ms — the execution environment's background time budget (${bgTimeout}s) is shorter than the ${timeoutMs}ms watch timeout${clampNote}, and the shorter of the two wins.`
|
|
146
|
+
: `The watch may run up to ${timeoutMs}ms${clampNote}.`;
|
|
147
147
|
const content = onNotify !== undefined
|
|
148
148
|
? `Monitoring in background; task_id=${taskId}. Each stdout line becomes a notification event (lines within ` +
|
|
149
149
|
`~200ms are batched); you'll get a final notification with the exit code when it ends — do not poll. ` +
|