@sema-agent/core 7.24.3 → 7.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.
Files changed (106) hide show
  1. package/CHANGELOG.md +101 -1
  2. package/dist/agents/peer-session-drain.d.ts +16 -1
  3. package/dist/agents/peer-session-drain.js +16 -1
  4. package/dist/agents/send-message-tool.d.ts +10 -2
  5. package/dist/agents/send-message-tool.js +18 -7
  6. package/dist/agents/subagent.js +13 -10
  7. package/dist/core/background-agent-store.d.ts +3 -3
  8. package/dist/core/checkpoint-store.d.ts +1 -0
  9. package/dist/core/delegation-frames.d.ts +5 -0
  10. package/dist/core/gate-fold.js +85 -0
  11. package/dist/core/gate-lanes.js +23 -1
  12. package/dist/core/gate-outcome.d.ts +8 -2
  13. package/dist/core/gate-outcome.js +3 -1
  14. package/dist/core/hooks.d.ts +29 -0
  15. package/dist/core/hooks.js +2 -0
  16. package/dist/core/mailbox-store.d.ts +5 -0
  17. package/dist/core/mailbox-store.js +1 -1
  18. package/dist/core/parent-spec-seat.d.ts +7 -1
  19. package/dist/core/parent-spec-seat.js +2 -0
  20. package/dist/core/permission-rule-consent.js +29 -47
  21. package/dist/core/permission-rule-provider.js +4 -3
  22. package/dist/core/permission-rule-store.d.ts +143 -7
  23. package/dist/core/permission-rule-store.js +165 -15
  24. package/dist/core/permission-rules.js +14 -10
  25. package/dist/core/rule-denial-sentence.d.ts +75 -0
  26. package/dist/core/rule-denial-sentence.js +16 -0
  27. package/dist/core/runner/contracts.d.ts +36 -2
  28. package/dist/core/runner/gate-exit.d.ts +14 -0
  29. package/dist/core/runner/gate-exit.js +4 -0
  30. package/dist/core/runner/prepare-delegation-surface.d.ts +4 -4
  31. package/dist/core/runner/prepare-delegation-surface.js +2 -1
  32. package/dist/core/runner/prepare-gate-stations.d.ts +4 -0
  33. package/dist/core/runner/prepare-gate-stations.js +7 -4
  34. package/dist/core/runner/prepare-hands-readface.d.ts +8 -4
  35. package/dist/core/runner/prepare-hands-readface.js +25 -10
  36. package/dist/core/runner/prepare-inherited-gate.d.ts +23 -0
  37. package/dist/core/runner/prepare-inherited-gate.js +11 -6
  38. package/dist/core/runner/prepare-policy-chain.d.ts +15 -1
  39. package/dist/core/runner/prepare-policy-chain.js +42 -22
  40. package/dist/core/runner/prepare-suspend-saga.d.ts +5 -2
  41. package/dist/core/runner/prepare-suspend-saga.js +4 -3
  42. package/dist/core/runner/prepare-task.js +10 -9
  43. package/dist/core/runner/prepare-wiring-manifest.d.ts +11 -4
  44. package/dist/core/runner/prepare-wiring-manifest.js +8 -4
  45. package/dist/core/runner/resume-apply.js +2 -0
  46. package/dist/core/runner/run-harness-handlers.js +1 -0
  47. package/dist/core/runner/run-identity-wiring.d.ts +2 -2
  48. package/dist/core/runner/run-leg.js +28 -1
  49. package/dist/core/runner/run-notification-lane.d.ts +2 -2
  50. package/dist/core/runner/run-notification-lane.js +2 -1
  51. package/dist/core/runner/run-settle-and-teardown.js +1 -0
  52. package/dist/core/runner/session-rule-policy.d.ts +73 -2
  53. package/dist/core/runner/session-rule-policy.js +121 -21
  54. package/dist/core/session-policy-store.d.ts +27 -3
  55. package/dist/core/session-policy-store.js +12 -4
  56. package/dist/core/store-contracts/background-agent-store-contract.js +10 -0
  57. package/dist/core/store-contracts/peer-directory-contract.d.ts +23 -0
  58. package/dist/core/store-contracts/peer-directory-contract.js +82 -0
  59. package/dist/core/task-event.d.ts +6 -0
  60. package/dist/core/task-notification.d.ts +79 -4
  61. package/dist/core/task-notification.js +90 -1
  62. package/dist/core/task-registry-agent.js +1 -2
  63. package/dist/core/task-registry-monitor.js +2 -1
  64. package/dist/core/task-registry-shared.d.ts +53 -5
  65. package/dist/core/task-registry-shared.js +13 -2
  66. package/dist/core/task-registry.js +8 -5
  67. package/dist/core/task-spec.d.ts +12 -5
  68. package/dist/core/tool-catalog-entries.js +1 -0
  69. package/dist/core/tool-spec.d.ts +1 -1
  70. package/dist/core/wiring-manifest.d.ts +2 -2
  71. package/dist/engine/loop/agent-loop.js +1 -1
  72. package/dist/engine/loop/types.d.ts +4 -0
  73. package/dist/index.d.ts +6 -3
  74. package/dist/index.js +4 -3
  75. package/dist/orchestration/workflow.js +12 -3
  76. package/dist/prompts/coordinator.d.ts +2 -1
  77. package/dist/stores/file/adoption/adopt.js +23 -1
  78. package/dist/stores/file/permission-rule-store.js +3 -1
  79. package/dist/stores/file/session-policy-store.d.ts +31 -0
  80. package/dist/stores/file/session-policy-store.js +87 -7
  81. package/dist/tools/fs/bash-program-position.d.ts +4 -26
  82. package/dist/tools/fs/bash-program-position.js +0 -11
  83. package/dist/tools/fs/bash-readonly-classifier.d.ts +20 -309
  84. package/dist/tools/fs/bash-readonly-classifier.js +28 -766
  85. package/dist/tools/fs/fs-bash.d.ts +43 -207
  86. package/dist/tools/fs/fs-bash.js +20 -341
  87. package/dist/tools/fs/index.d.ts +12 -13
  88. package/dist/tools/fs/index.js +3 -4
  89. package/dist/tools/fs/read-deny.d.ts +25 -0
  90. package/dist/tools/fs/read-deny.js +19 -0
  91. package/dist/tools/fs/safety.js +2 -6
  92. package/dist/tools/fs/shell-read-check.d.ts +99 -0
  93. package/dist/tools/fs/shell-read-check.js +229 -0
  94. package/dist/tools/fs/shell-read-establish.d.ts +70 -115
  95. package/dist/tools/fs/shell-read-establish.js +216 -102
  96. package/dist/tools/fs/shell-read-judge.d.ts +231 -0
  97. package/dist/tools/fs/shell-read-judge.js +170 -0
  98. package/dist/tools/fs/shell-read-plan.d.ts +3 -0
  99. package/dist/tools/fs/shell-read-plan.js +82 -19
  100. package/dist/tools/monitor.d.ts +5 -5
  101. package/dist/tools/monitor.js +3 -5
  102. package/dist/tools/task-list.d.ts +4 -0
  103. package/package.json +1 -1
  104. package/test/export-surface.snapshot.json +19 -3
  105. package/wire-changes/core@7.24.4.json +54 -0
  106. package/wire-changes/core@7.25.0.json +671 -0
@@ -1180,7 +1180,7 @@ export interface ToolExecuteContext {
1180
1180
  */
1181
1181
  onTaskNotification?: (n: import("./task-notification.js").TaskNotificationPayload, opts?: {
1182
1182
  priority?: import("./task-notification.js").SystemInjectionPriority;
1183
- }) => void;
1183
+ }) => unknown;
1184
1184
  /**
1185
1185
  * design/99 (nested-subagent live observability): the host run's OPT-IN display sink — a deployment sets it (via
1186
1186
  * `RunInternals.onForwardEvent`) to receive a SUBAGENT's live `task_progress` ticks that otherwise stay in the
@@ -432,8 +432,8 @@ export interface WiringFacts {
432
432
  parkDurableApprovalOptIn: boolean;
433
433
  /** `undefined` = unresolved at this half (per-principal runtime caps are a prepare-time fact). */
434
434
  parkForceDurableGate?: boolean;
435
- /** `undefined` = unresolved at this half (the irreversible/egress tool vocabulary is assembled in
436
- * prepare). */
435
+ /** `undefined` = unresolved at this half (the irreversible/egress tool vocabulary and the shell read station —
436
+ * the three sources of a mandated ask — are assembled in prepare). */
437
437
  parkSafetyVocabularyArmed?: boolean;
438
438
  /** Present iff `parkCapable` (the resolved checkpoint store's declaration). */
439
439
  checkpointDurability?: StoreDurability;
@@ -1325,7 +1325,7 @@ async function prepareToolCall(currentContext, assistantMessage, toolCall, confi
1325
1325
  };
1326
1326
  }
1327
1327
  if (beforeResult?.block) {
1328
- const blocked = createErrorToolResult(beforeResult.reason || "Tool execution was blocked");
1328
+ const blocked = createErrorToolResult(beforeResult.reason || "Tool execution was blocked", beforeResult.details !== undefined ? { details: beforeResult.details } : undefined);
1329
1329
  if (beforeResult.haltRemaining !== undefined) {
1330
1330
  return { kind: "immediate", result: blocked, isError: true, halt: beforeResult.haltRemaining };
1331
1331
  }
@@ -83,6 +83,10 @@ export type AgentToolCall = Extract<AssistantMessage["content"][number], {
83
83
  export interface BeforeToolCallResult {
84
84
  block?: boolean;
85
85
  reason?: string;
86
+ /** With `block`: the structured details the refusing layer minted, attached to the error result the loop
87
+ * builds from `reason` (a `{ type, code, … }` card reaches `tool_end.structured` / `errorCode` exactly as a
88
+ * tool's own refusal would). Ignored without `block`. */
89
+ details?: unknown;
86
90
  updatedInput?: unknown;
87
91
  haltRemaining?: HaltRemainingDirective;
88
92
  }
package/dist/index.d.ts CHANGED
@@ -100,7 +100,7 @@ export { sanitizeUntrustedText, delimitUntrusted, inlineUntrusted } from "./core
100
100
  export { mintReminderMark, isValidReminderMark, openSystemReminder, mintSystemReminder, reminderMarkDeclaration } from "./core/reminder-mint.js";
101
101
  export { deriveInvariants, checkInvariants } from "./core/property-harness.js";
102
102
  export type { InvariantKind, FunctionContract, Invariant, InvariantViolation, CheckResult, } from "./core/property-harness.js";
103
- export { bashReversibilityProbe, bashReadBoundaryProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
103
+ export { bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
104
104
  export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, type BashReadonlyRootBoundary, type CompoundReadonlyVerdict, } from "./tools/fs/index.js";
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";
@@ -132,7 +132,9 @@ export { createSensitivePathPolicy, RECOMMENDED_SENSITIVE_PATTERNS } from "./cor
132
132
  export { createFsWriteGatePolicy, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE, type FsWriteGatePolicyOptions } from "./core/fs-write-gate-policy.js";
133
133
  export { RETIRED_TOOL_NAMES } from "./core/tool-name-aliases.js";
134
134
  export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
135
- export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, type TaskNotificationPayload, type TaskNotificationStatus, type ExternalNotificationInput, type SystemInjection, type SystemInjectionPriority, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, type SemaProvenance, } from "./core/task-notification.js";
135
+ export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, type TaskNotificationPayload, type TaskNotificationStatus, type ExternalNotificationInput, type SystemInjection, type SystemInjectionPriority, type NotificationInjectionOutcome, // the injector's outcome words (`parked_durably` = the session's durable box took the frame)
136
+ NOTIFICATION_INJECTION_OUTCOMES, // …and their value half + guard, so a host wiring its own uplink validates a reported outcome without duplicating the vocabulary
137
+ isNotificationInjectionOutcome, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, type SemaProvenance, } from "./core/task-notification.js";
136
138
  export { TOOL_FAMILIES, TOOL_MOUNT_TAGS, TOOL_APPROVAL_CARDS, TOOL_PATH_ACCESSES, TOOL_PATH_BASES, TOOL_PATH_ABSENCES, TOOL_WIRE_NAME_MAX_CHARS, TOOL_CONTRACT_MAX_CHARS, TOOL_CARD_ID_MAX_CHARS, TOOL_KEY_MAX_CHARS, TOOL_KEYS_MAX, RENDER_HINT_MAX_CHARS, RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_LIST, type ToolFamily, type ToolMountTag, type ToolApprovalCard, type ToolPathAccess, type ToolPathBase, type ToolPathAbsence, type ToolPathTarget, type ToolCallPathTarget, type ToolRosterPathTarget, pathTargetSlotOnly, type ToolRuleFace, type ToolRenderHints, type ToolFaceDeclaration, type ToolCatalogEntry, } from "./core/tool-face.js";
137
139
  export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRuleFaceOf, pathTargetOf, pathTargetValue, protectivePathTargetOf, declaredPathTargetOf, BARE_REQUEST_CODE, isProtectedWrite, isDeclaredWrite, fileOpAccessOf, type ToolCallLike, pathConfinableWriteToolNames, skillScopeWriteToolNames, pathTargetingToolNames, compactableToolNames, budgetExemptToolNames, handBandEffects, engineCardTypes, predictMountedToolNames, HANDS_BAND_TAGS, MOUNT_TAG_IN_HANDS_BAND, } from "./core/tool-registry.js";
138
140
  export { ToolRosterEntry, ToolRoster, ToolRosterDelta, ToolRosterRenderHints, ROSTER_SOURCES, type RosterSource, type ToolIdentity, toolIdentityOf, mintCapabilityId, diffToolRoster, judgeParkedToolIdentity, ruleFacesFromRoster, toolCallFaceOf, inputKeysOf, ruleFaceOfInstance, rosterMemberBoundProblem, type RosterBoundMembers, type RosterBoundCode, ToolRosterDeltaSeat, } from "./core/tool-roster.js";
@@ -201,7 +203,7 @@ export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRul
201
203
  * allowed and widening is not.
202
204
  */
203
205
  export { RULE_BEHAVIORS, RULE_BEHAVIOR_PRECEDENCE, RULE_BEHAVIORS_BY_PRECEDENCE, isRuleBehavior, readRowBehavior, adjudicatePersistedRules, adjudicatePersistedPathRules, ruleReachesProgramRun, programRunReachOf, PROGRAM_RUN_REACHES, type ProgramRunReachOutcome, pathRuleReachOf, ruleBasesNeeded, PATH_RULE_BASES, PATH_RULE_BASE_LABEL, ruleToolGrammarOf, COMMAND_RULE_TOOL, READ_RULE_TOOL, type PathRuleBase, type PathRuleBases, type RuleBehavior, type PersistedRuleVerdict, parseRuleText, formatRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, renderUntrustedCommandText, stripFormatCharacters, UNCOVERED_SEGMENT_REASON_BASELINE, RULE_OFFERS_ABSENCE_BASELINE, type UncoveredSegmentDetail, type EditedRuleBreadthWarning, directoryRuleAdmits, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, type PersistedRule, type RuleTombstone, type RuleScope, type RuleDot, type RuleAdd, type RuleAddOrigin, type RuleOffer, type RuleOfferBatchMember, type SegmentRuleSuggestion, type SegmentCoverage, type RuleReject, type RuleRejectCode, type ParsedRule, type PersistedRuleTool, type PersistedRuleMatch, } from "./core/permission-rule-model.js";
204
- export { removePersistedRule, applyTombstones, sameScope, sameRuleIdentity, isValidConsentScope, isValidDurableScope, InMemoryDurableRulePartition, EMPTY_DURABLE_RULE_PARTITION, type DurableRulePartition, type DurableRulePartitionProvider, type RuleWriteOutcome, type StoredRules, 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, assertWriteDeltaScopeDurable, type PermissionRuleWriter, type WritableDurableRulePartition, type RuleWriteDelta, type RuleAddDelta, type RuleDeleteDelta, type RuleSyncJoinDelta, type RawRuleSyncState, type RedemptionAuthorization, } from "./core/permission-rule-store.js";
206
+ export { removePersistedRule, addPersistedRule, applyTombstones, sameScope, sameRuleIdentity, isValidConsentScope, isValidDurableScope, InMemoryDurableRulePartition, EMPTY_DURABLE_RULE_PARTITION, type DurableRulePartition, type DurableRulePartitionProvider, type RuleWriteOutcome, type StoredRules, type RemoveResult, type AddResult, 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, assertWriteDeltaScopeDurable, type PermissionRuleWriter, type WritableDurableRulePartition, type RuleWriteDelta, type RuleAddDelta, type RuleTightenAddDelta, type RuleDeleteDelta, type RuleSyncJoinDelta, type RawRuleSyncState, type RedemptionAuthorization, } from "./core/permission-rule-store.js";
205
207
  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";
206
208
  export { createPermissionRuleStoreProvider, effectivePermissionRules, effectiveOrThrow, ruleSourceOf, type PermissionRuleStore, type PermissionRuleStoreProvider, type PermissionRuleStoreConfig, type EffectivePermissionRules, type EffectivePermissionRule, type RemovedPermissionRule, type RuleSource, } from "./core/permission-rule-provider.js";
207
209
  export { InMemorySessionRulePartition, type SessionRulePartition, type SessionRuleAdd, type SessionRuleApplyResult, } from "./core/permission-rule-session.js";
@@ -277,6 +279,7 @@ export { workflowJournalStoreContract } from "./core/store-contracts/workflow-jo
277
279
  export { permissionRuleSyncContract, type PermissionRuleSyncContractHooks } from "./core/store-contracts/permission-rule-sync-contract.js";
278
280
  export { MAILBOX_CONTRACT_SCOPE, mailboxStoreContract, mailboxAckOwnershipContract, mailboxBundledOnlyContract, mailboxTombstonedRecipientContract, type MailboxTombstonedRecipientContractHooks, mailboxCrossProcessContract, type MailboxCrossProcessContractHooks, } from "./core/store-contracts/mailbox-store-contract.js";
279
281
  export { BACKGROUND_AGENT_CONTRACT_SCOPE, backgroundAgentStoreContract, backgroundAgentStoreScopesContract, } from "./core/store-contracts/background-agent-store-contract.js";
282
+ export { PEER_DIRECTORY_CONTRACT_SCOPE, peerDirectoryContract, type PeerDirectoryContractHooks } from "./core/store-contracts/peer-directory-contract.js";
280
283
  export { type BackgroundAgentStore, type BackgroundAgentRecord, type BackgroundAgentRowSummary, type BackgroundAgentUsage, canAccessAgentRecord, STALE_RUNNING_REAP_ATTRIBUTION, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, queryBackgroundAgents, type BackgroundAgentQuery, } from "./core/background-agent-store.js";
281
284
  export { serveDurableAgentRowLane, buildAgentPollDetails, type AgentPollDetailsInput, } from "./core/task-registry-agent.js";
282
285
  export type { UnifiedTaskResult } from "./core/task-registry-shared.js";
package/dist/index.js CHANGED
@@ -78,7 +78,7 @@ export { runExecGate } from "./core/exec-gate.js";
78
78
  export { sanitizeUntrustedText, delimitUntrusted, inlineUntrusted } from "./core/untrusted-text.js";
79
79
  export { mintReminderMark, isValidReminderMark, openSystemReminder, mintSystemReminder, reminderMarkDeclaration } from "./core/reminder-mint.js";
80
80
  export { deriveInvariants, checkInvariants } from "./core/property-harness.js";
81
- export { bashReversibilityProbe, bashReadBoundaryProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
81
+ export { bashReversibilityProbe, BASH_READONLY_DEFAULT_ALLOW, parseLeadingCommandName, classifyCompoundReadonly, MAX_EDIT_BYTES } from "./tools/fs/index.js";
82
82
  export { classifyCompoundReadonlyDetailed, formatOutOfRootReadApprovalOption, } from "./tools/fs/index.js";
83
83
  export { resolveBashTimeoutCaps } from "./tools/fs/index.js";
84
84
  export { READ_FACE_DEFAULT_DENY_ENTRIES, READ_FACE_BUILTIN_DENY_TABLE, READ_DENY_BUILTIN_TIERS, READ_DENY_DEFAULT_TIERS, resolveReadDenyBuiltins, compileReadDeny, } from "./tools/fs/index.js";
@@ -107,7 +107,7 @@ export { createSensitivePathPolicy, RECOMMENDED_SENSITIVE_PATTERNS } from "./cor
107
107
  export { createFsWriteGatePolicy, WRITE_EXEMPT_DIR_COVERS_ROOT_CODE } from "./core/fs-write-gate-policy.js";
108
108
  export { RETIRED_TOOL_NAMES } from "./core/tool-name-aliases.js";
109
109
  export { DEFAULT_SUBAGENT_TOOL_NAME } from "./agents/subagent.js";
110
- export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, } from "./core/task-notification.js";
110
+ export { renderTaskNotificationXml, taskNotificationDedupKey, isDelegatedAgentTerminal, isTerminalTaskNotification, SystemInjectionQueue, NOTIFICATION_INJECTION_OUTCOMES, isNotificationInjectionOutcome, SYSTEM_INJECTION_PRIORITIES, isSystemInjectionPriority, AGENT_MESSAGE_TAG, renderAgentMessageFrame, } from "./core/task-notification.js";
111
111
  export { TOOL_FAMILIES, TOOL_MOUNT_TAGS, TOOL_APPROVAL_CARDS, TOOL_PATH_ACCESSES, TOOL_PATH_BASES, TOOL_PATH_ABSENCES, TOOL_WIRE_NAME_MAX_CHARS, TOOL_CONTRACT_MAX_CHARS, TOOL_CARD_ID_MAX_CHARS, TOOL_KEY_MAX_CHARS, TOOL_KEYS_MAX, RENDER_HINT_MAX_CHARS, RENDER_HINT_ACTIVITY_MAX_CHARS, RENDER_HINT_MAX_LIST, pathTargetSlotOnly, } from "./core/tool-face.js";
112
112
  export { describeToolCatalog, catalogEntriesNamed, catalogToolNames, catalogRuleFaceOf, pathTargetOf, pathTargetValue, protectivePathTargetOf, declaredPathTargetOf, BARE_REQUEST_CODE, isProtectedWrite, isDeclaredWrite, fileOpAccessOf, pathConfinableWriteToolNames, skillScopeWriteToolNames, pathTargetingToolNames, compactableToolNames, budgetExemptToolNames, handBandEffects, engineCardTypes, predictMountedToolNames, HANDS_BAND_TAGS, MOUNT_TAG_IN_HANDS_BAND, } from "./core/tool-registry.js";
113
113
  export { ToolRosterEntry, ToolRoster, ToolRosterDelta, ToolRosterRenderHints, ROSTER_SOURCES, toolIdentityOf, mintCapabilityId, diffToolRoster, judgeParkedToolIdentity, ruleFacesFromRoster, toolCallFaceOf, inputKeysOf, ruleFaceOfInstance, rosterMemberBoundProblem, ToolRosterDeltaSeat, } from "./core/tool-roster.js";
@@ -159,7 +159,7 @@ export { AUTO_MODE_ARMING_RECIPE_VERSION, autoModeArmingRecipeOf, sanitizeAutoMo
159
159
  export { rebuildAutoModeDecider, } from "./core/auto-mode-rebuild.js";
160
160
  export { createPermissionRulePolicy, validatePermissionRules, parsePermissionRule, wildcardMatch, isNamespacedCoveringRuleName, namespacedRuleNameCovers, } from "./core/permission-rules.js";
161
161
  export { RULE_BEHAVIORS, RULE_BEHAVIOR_PRECEDENCE, RULE_BEHAVIORS_BY_PRECEDENCE, isRuleBehavior, readRowBehavior, adjudicatePersistedRules, adjudicatePersistedPathRules, ruleReachesProgramRun, programRunReachOf, PROGRAM_RUN_REACHES, pathRuleReachOf, ruleBasesNeeded, PATH_RULE_BASES, PATH_RULE_BASE_LABEL, ruleToolGrammarOf, COMMAND_RULE_TOOL, READ_RULE_TOOL, parseRuleText, formatRuleText, ruleAdmitsCommand, findAdmittingRule, suggestRulesForCommand, scopeCoversCwd, pathWithinRoot, isRuleLive, renderUntrustedCommandText, stripFormatCharacters, UNCOVERED_SEGMENT_REASON_BASELINE, RULE_OFFERS_ABSENCE_BASELINE, directoryRuleAdmits, BARE_INTERPRETER_NAMES, MAX_RULE_TEXT_CHARS, } from "./core/permission-rule-model.js";
162
- export { removePersistedRule, applyTombstones, sameScope, sameRuleIdentity, isValidConsentScope, isValidDurableScope, InMemoryDurableRulePartition, EMPTY_DURABLE_RULE_PARTITION, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, } from "./core/permission-rule-store.js";
162
+ export { removePersistedRule, addPersistedRule, applyTombstones, sameScope, sameRuleIdentity, isValidConsentScope, isValidDurableScope, InMemoryDurableRulePartition, EMPTY_DURABLE_RULE_PARTITION, joinRuleStates, screenRuleSyncState, collectBelowFrontier, ruleSyncVector, joinFrontiers, dotAtOrBelowFrontier, sameRuleOwner, PERMISSION_RULE_WRITER, writerOf, foldDelta, addDotsOf, assertDeleteDeltaCarriesNoAdd, assertRedemptionNotQuarantined, assertWriteDeltaScopeDurable, } from "./core/permission-rule-store.js";
163
163
  export { syncPermissionRules, parseRuleSyncResponse, PERMISSION_RULE_SYNC_PATH, LOCAL_OWNER_UNSYNCABLE_CODE, } from "./core/permission-rule-sync.js";
164
164
  export { createPermissionRuleStoreProvider, effectivePermissionRules, effectiveOrThrow, ruleSourceOf, } from "./core/permission-rule-provider.js";
165
165
  export { InMemorySessionRulePartition, } from "./core/permission-rule-session.js";
@@ -233,6 +233,7 @@ export { workflowJournalStoreContract } from "./core/store-contracts/workflow-jo
233
233
  export { permissionRuleSyncContract } from "./core/store-contracts/permission-rule-sync-contract.js";
234
234
  export { MAILBOX_CONTRACT_SCOPE, mailboxStoreContract, mailboxAckOwnershipContract, mailboxBundledOnlyContract, mailboxTombstonedRecipientContract, mailboxCrossProcessContract, } from "./core/store-contracts/mailbox-store-contract.js";
235
235
  export { BACKGROUND_AGENT_CONTRACT_SCOPE, backgroundAgentStoreContract, backgroundAgentStoreScopesContract, } from "./core/store-contracts/background-agent-store-contract.js";
236
+ export { PEER_DIRECTORY_CONTRACT_SCOPE, peerDirectoryContract } from "./core/store-contracts/peer-directory-contract.js";
236
237
  export { canAccessAgentRecord, STALE_RUNNING_REAP_ATTRIBUTION, BackgroundAgentStoreError, InMemoryBackgroundAgentStore, reconcileParkedAgents, queryBackgroundAgents, } from "./core/background-agent-store.js";
237
238
  export { serveDurableAgentRowLane, buildAgentPollDetails, } from "./core/task-registry-agent.js";
238
239
  export { FileBackgroundAgentStore } from "./stores/file/background-agent-store.js";
@@ -714,7 +714,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
714
714
  if (!bceSink)
715
715
  return;
716
716
  const id = waIdOf(callKey);
717
- bceLive.set(id, { callKey, label, ...(agentType !== undefined ? { agentType } : {}), ...(sessionId !== undefined ? { sessionId } : {}), ...(model !== undefined ? { model } : {}) });
717
+ bceLive.set(id, { callKey, label, ...(agentType !== undefined ? { agentType } : {}), ...(sessionId !== undefined ? { sessionId } : {}), ...(model !== undefined ? { model } : {}), generation: 1 });
718
718
  bceEmit({
719
719
  kind: "spawn",
720
720
  taskId: id,
@@ -732,6 +732,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
732
732
  ...(opts.originatingSessionId !== undefined ? { parentSessionId: opts.originatingSessionId } : {}),
733
733
  ...((opts.rootSessionId ?? opts.originatingSessionId) !== undefined ? { rootSessionId: opts.rootSessionId ?? opts.originatingSessionId } : {}),
734
734
  startedAt: Date.now(),
735
+ seq: 1,
735
736
  });
736
737
  };
737
738
  const bceBindSession = (callKey, sessionId) => {
@@ -739,6 +740,11 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
739
740
  if (row !== undefined)
740
741
  row.sessionId = sessionId;
741
742
  };
743
+ const bceGeneration = (callKey, generation) => {
744
+ const row = bceLive.get(waIdOf(callKey));
745
+ if (row !== undefined)
746
+ row.generation = generation;
747
+ };
742
748
  const bceTick = (callKey, e, fromSessionId) => {
743
749
  if (!bceSink)
744
750
  return;
@@ -763,6 +769,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
763
769
  progressTaskId: e.taskId,
764
770
  ...(e.parentTaskId !== undefined ? { progressParentTaskId: e.parentTaskId } : {}),
765
771
  ...(e.currentAction !== undefined ? { currentAction: e.currentAction } : {}),
772
+ seq: row.generation,
766
773
  usage: e.usage,
767
774
  });
768
775
  };
@@ -771,9 +778,9 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
771
778
  return;
772
779
  const id = waIdOf(callKey);
773
780
  const row = bceLive.get(id);
774
- if (!bceLive.delete(id))
781
+ if (row === undefined || !bceLive.delete(id))
775
782
  return;
776
- const coord = sessionId ?? row?.sessionId;
783
+ const coord = sessionId ?? row.sessionId;
777
784
  bceEmit({
778
785
  kind: "terminal",
779
786
  taskId: id,
@@ -784,6 +791,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
784
791
  ...(scope !== undefined ? { scope } : {}),
785
792
  ...(coord !== undefined ? { sessionId: coord, transcriptId: coord } : {}),
786
793
  status,
794
+ seq: row.generation,
787
795
  summary: boundedRedactedSummary(summary, 300),
788
796
  ...(stats !== undefined ? { usage: { tokens: stats.tokens, turns: stats.turns, ...(stats.costMicroUsd !== undefined ? { costMicroUsd: stats.costMicroUsd } : {}) } } : {}),
789
797
  });
@@ -1575,6 +1583,7 @@ export function startWorkflow(runner, fn, opts = {}, internals) {
1575
1583
  let approvalWaivedAttempts = 0;
1576
1584
  for (;;) {
1577
1585
  attempts += 1;
1586
+ bceGeneration(callKey, attempts);
1578
1587
  const attemptStart = now();
1579
1588
  const attemptCtl = new AbortController();
1580
1589
  let stallFired = false;
@@ -2,7 +2,8 @@
2
2
  * design/147 S3b — the teammate communication addendum (CC 2.1.212
3
3
  * `TEAMMATE_SYSTEM_PROMPT_ADDENDUM`, pretty.js:322948, sema-ized): composed for a NAMED delegated
4
4
  * child (`core/mode.teammate` section, admit = facts.teammateEnabled — the CC injection condition
5
- * `agentId && agentName && teamName` maps to sema's named-spawn internals chain). Divergences:
5
+ * `agentId && agentName && teamName` maps to sema's named-spawn internals chain; sema keeps no team
6
+ * name — one implicit team per session, keyed by the root session). Divergences:
6
7
  * CC's "task system"/"team lead" wording maps to sema's `"main"` reserved lane (the spawning
7
8
  * conversation); and the capability sentences track the shipped contract (S2a
8
9
  * delivered mid-run delivery): a message to a RUNNING teammate is queued and reaches it at its next
@@ -3,6 +3,7 @@ import { existsSync, readdirSync, readFileSync, unlinkSync } from "node:fs";
3
3
  import { join, resolve } from "node:path";
4
4
  import { adoptFilePermissionRuleStore } from "../permission-rule-adopt.js";
5
5
  import { BootLock, atomicWriteFile, ensureDir, sanitizeScope } from "../fs-atomic.js";
6
+ import { SESSION_POLICY_MARK_SUFFIX, readSessionPolicyMark } from "../session-policy-store.js";
6
7
  import { thrownText } from "../../../core/thrown-value.js";
7
8
  import { AdoptionError, NOT_MIGRATED_BY_DESIGN, readRootAdoptionFile, writeRootAdoptionFile, } from "./marker.js";
8
9
  const CONFIG_ACCOUNT_FILE = "adoption-configs.json";
@@ -194,6 +195,10 @@ function rewriteSessionPolicyRows(root, fromPrincipal, toPrincipal) {
194
195
  for (const file of files) {
195
196
  if (!file.endsWith(".json"))
196
197
  continue;
198
+ if (file.endsWith(SESSION_POLICY_MARK_SUFFIX)) {
199
+ carrySessionPolicyMark(dir, tmpDir, file, fromPrincipal, toPrincipal);
200
+ continue;
201
+ }
197
202
  const source = join(dir, file);
198
203
  let raw;
199
204
  try {
@@ -218,8 +223,9 @@ function rewriteSessionPolicyRows(root, fromPrincipal, toPrincipal) {
218
223
  continue;
219
224
  if (typeof row.__sid !== "string")
220
225
  continue;
226
+ const stem = sanitizeScope(JSON.stringify([row.__sid, toPrincipal]));
221
227
  const rewritten = JSON.stringify({ ...row, __principal: toPrincipal });
222
- const target = join(dir, `${sanitizeScope(JSON.stringify([row.__sid, toPrincipal]))}.json`);
228
+ const target = join(dir, `${stem}.json`);
223
229
  if (existsSync(target)) {
224
230
  if (readFileSync(target, "utf8") === rewritten) {
225
231
  removeIfPresent(source);
@@ -235,6 +241,22 @@ function rewriteSessionPolicyRows(root, fromPrincipal, toPrincipal) {
235
241
  }
236
242
  return rows;
237
243
  }
244
+ function carrySessionPolicyMark(dir, tmpDir, file, fromPrincipal, toPrincipal) {
245
+ const source = join(dir, file);
246
+ const mark = readSessionPolicyMark(source);
247
+ if (mark === null)
248
+ return;
249
+ if (mark.__principal !== fromPrincipal)
250
+ return;
251
+ if (typeof mark.__sid !== "string")
252
+ return;
253
+ const target = join(dir, `${sanitizeScope(JSON.stringify([mark.__sid, toPrincipal]))}${SESSION_POLICY_MARK_SUFFIX}`);
254
+ const there = readSessionPolicyMark(target);
255
+ const merged = there === null ? mark.__hwm : Math.max(mark.__hwm, there.__hwm);
256
+ const mergedGen = Math.max(mark.__gen ?? 0, there?.__gen ?? 0);
257
+ atomicWriteFile(tmpDir, target, JSON.stringify({ ...mark, __principal: toPrincipal, __hwm: merged, __gen: mergedGen }));
258
+ removeIfPresent(source);
259
+ }
238
260
  function removeIfPresent(path) {
239
261
  try {
240
262
  unlinkSync(path);
@@ -311,7 +311,9 @@ class FileDurableRulePartition {
311
311
  }
312
312
  const next = delta.kind === "redemption-add"
313
313
  ? (assertWriteDeltaScopeDurable(delta), assertRedemptionNotQuarantined(cur.quarantined ?? [], delta), { ...cur, rev: cur.rev + 1, rules: foldDelta(cur.rules, delta) })
314
- : (assertWriteDeltaScopeDurable(delta), assertDeleteDeltaCarriesNoAdd(delta), { ...cur, rev: cur.rev + 1, tombstones: [...cur.tombstones, delta.tombstone] });
314
+ : delta.kind === "tighten-add"
315
+ ? (assertWriteDeltaScopeDurable(delta), { ...cur, rev: cur.rev + 1, rules: foldDelta(cur.rules, delta) })
316
+ : (assertWriteDeltaScopeDurable(delta), assertDeleteDeltaCarriesNoAdd(delta), { ...cur, rev: cur.rev + 1, tombstones: [...cur.tombstones, delta.tombstone] });
315
317
  if (!this.writeAndVerify(next)) {
316
318
  throw new Error("the permission-rule store did not survive its own write; the change was not committed");
317
319
  }
@@ -15,6 +15,32 @@ export interface FileSessionPolicyStoreOptions {
15
15
  * Never fires on plain ENOENT. Swallow-guarded: a throwing sink cannot re-introduce the fault. */
16
16
  onCorruptRead?: (info: SessionPolicyCorruptReadInfo) => void;
17
17
  }
18
+ /**
19
+ * The suffix that tells a MARK file apart from a rules ROW in the shared directory — injective by
20
+ * construction, not by convention: `sanitizeScope` ends every row's basename with a 64-hex digest, so the
21
+ * character before a row's `.json` is always a hex digit and no row can be spelled `*.hwm.json`. Every sweep
22
+ * of this directory tests it FIRST: a mark must never be listed as a row, nor judged as crash litter.
23
+ */
24
+ export declare const SESSION_POLICY_MARK_SUFFIX = ".hwm.json";
25
+ /** What one mark file carries: the number, plus the row's attribution fields (the filename is a one-way hash,
26
+ * so those are the only way a sweep can tell whose mark it is). Attribution is OPTIONAL on the read side —
27
+ * this store always writes it, a hand-written file need not, and a mark with no owner is simply nobody's. */
28
+ export interface StoredHighWaterMark {
29
+ __hwm: number;
30
+ /** The lineage the key's NEXT row starts (bumped by every erase); absent = 0 (a mark written before lineages). */
31
+ __gen?: number;
32
+ __sid?: string;
33
+ __principal?: string | null;
34
+ }
35
+ /**
36
+ * The ONE reader of a mark file — the number it holds, or `null` when there is no file (the ordinary state of
37
+ * a key nobody erased). Every other outcome is a refusal, and that is the point: for a ROW "unreadable"
38
+ * degrades to "absent" and costs nothing, while for a mark absence reads as ZERO, which is precisely the
39
+ * rollback the erase contract forbids. Bytes that are not a mark raise `SessionPolicyError("corrupt")` naming
40
+ * the file; an IO failure propagates as it already does for a row. Exported because ownership adoption moves
41
+ * these files too, and a second, more tolerant reader over there is how the guarantee goes quietly missing.
42
+ */
43
+ export declare function readSessionPolicyMark(path: string): StoredHighWaterMark | null;
18
44
  export declare class FileSessionPolicyStore implements SessionPolicyStore {
19
45
  private readonly dir;
20
46
  private readonly onCorruptRead;
@@ -40,6 +66,11 @@ export declare class FileSessionPolicyStore implements SessionPolicyStore {
40
66
  * composite key, so distinct keys never collide on disk). On a local-owner-adopted root the
41
67
  * anonymous lane resolves to the adopted principal's key (#132 — see {@link anonymousAlias}). */
42
68
  private pathFor;
69
+ /** The shared, injective stem of a key's two files — the row (`<stem>.json`) and its mark
70
+ * (`<stem>${MARK_SUFFIX}`), so the pair can never drift onto different keys. */
71
+ private basenameFor;
72
+ private markPathFor;
73
+ private writeMarkFile;
43
74
  private read;
44
75
  getRules(sessionId: string, principal?: string): Promise<StoredSessionRules | null>;
45
76
  putRules(sessionId: string, principal: string | undefined, rules: SessionPermissionRules, opts?: PutRulesOptions): Promise<StoredSessionRules>;
@@ -1,4 +1,4 @@
1
- import { readFileSync, readdirSync, unlinkSync } from "node:fs";
1
+ import { existsSync, 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";
@@ -9,6 +9,37 @@ function containSinkThenable(r) {
9
9
  });
10
10
  }
11
11
  }
12
+ export const SESSION_POLICY_MARK_SUFFIX = ".hwm.json";
13
+ const MARK_SUFFIX = SESSION_POLICY_MARK_SUFFIX;
14
+ export function readSessionPolicyMark(path) {
15
+ let raw;
16
+ try {
17
+ raw = readFileSync(path, "utf8");
18
+ }
19
+ catch (err) {
20
+ if (err.code === "ENOENT")
21
+ return null;
22
+ throw err;
23
+ }
24
+ const refuse = (fault) => {
25
+ throw new SessionPolicyError("corrupt", `session rules high-water mark ${fault}: ${path} — refusing rather than restart this key's rev from zero. Deleting that file is the repair, and it gives up the number it was holding.`);
26
+ };
27
+ let parsed;
28
+ try {
29
+ parsed = JSON.parse(raw);
30
+ }
31
+ catch {
32
+ return refuse("is unparseable");
33
+ }
34
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
35
+ return refuse("is not a JSON object");
36
+ const mark = parsed;
37
+ if (typeof mark.__hwm !== "number" || !Number.isFinite(mark.__hwm))
38
+ return refuse("carries no numeric value");
39
+ if (mark.__gen !== undefined && (typeof mark.__gen !== "number" || !Number.isInteger(mark.__gen) || mark.__gen < 0))
40
+ return refuse("carries a lineage that is not a whole number");
41
+ return mark;
42
+ }
12
43
  export class FileSessionPolicyStore {
13
44
  dir;
14
45
  onCorruptRead;
@@ -33,9 +64,19 @@ export class FileSessionPolicyStore {
33
64
  this.disclose({ sessionId, ...(principal !== undefined ? { principal } : {}), path: this.pathFor(sessionId, principal), reason });
34
65
  }
35
66
  pathFor(sessionId, principal) {
67
+ return join(this.dir, `${this.basenameFor(sessionId, principal)}.json`);
68
+ }
69
+ basenameFor(sessionId, principal) {
36
70
  const effective = principal ?? this.anonymousAlias;
37
71
  const composite = JSON.stringify([sessionId, effective ?? null]);
38
- return join(this.dir, `${sanitizeScope(composite)}.json`);
72
+ return sanitizeScope(composite);
73
+ }
74
+ markPathFor(sessionId, principal) {
75
+ return join(this.dir, `${this.basenameFor(sessionId, principal)}${MARK_SUFFIX}`);
76
+ }
77
+ writeMarkFile(path, rev, sessionId, principal, gen) {
78
+ const mark = { __hwm: rev, __gen: gen, __sid: sessionId, __principal: principal };
79
+ atomicWriteFile(join(this.dir, "tmp"), path, JSON.stringify(mark));
39
80
  }
40
81
  read(sessionId, principal) {
41
82
  let raw;
@@ -54,8 +95,12 @@ export class FileSessionPolicyStore {
54
95
  return null;
55
96
  }
56
97
  const r = parsed;
57
- if (r.rev !== undefined && (typeof r.rev !== "number" || !Number.isFinite(r.rev))) {
58
- this.discloseCorrupt(sessionId, principal, "non-numeric rev");
98
+ if (typeof r.rev !== "number" || !Number.isFinite(r.rev) || r.rev < 1) {
99
+ this.discloseCorrupt(sessionId, principal, "rev is not a positive number");
100
+ return null;
101
+ }
102
+ if (r.gen !== undefined && (typeof r.gen !== "number" || !Number.isInteger(r.gen) || r.gen < 0)) {
103
+ this.discloseCorrupt(sessionId, principal, "gen is not a whole number");
59
104
  return null;
60
105
  }
61
106
  const strArr = (x) => x === undefined || (Array.isArray(x) && x.every((e) => typeof e === "string"));
@@ -88,8 +133,13 @@ export class FileSessionPolicyStore {
88
133
  throw new SessionPolicyError("loosen_forbidden", `a non-operator session-rule write may only tighten; refused: ${loosens.join("; ")}`);
89
134
  }
90
135
  }
91
- const next = { ...clean, rev: (prior?.rev ?? 0) + 1 };
92
- atomicWriteFile(join(this.dir, "tmp"), this.pathFor(sessionId, principal), JSON.stringify({ ...next, __sid: sessionId, __principal: (principal ?? this.anonymousAlias) ?? null }));
136
+ const mark = readSessionPolicyMark(this.markPathFor(sessionId, principal));
137
+ const floor = Math.max(prior?.rev ?? 0, mark?.__hwm ?? 0);
138
+ const gen = prior?.gen ?? (existsSync(this.pathFor(sessionId, principal)) ? (mark?.__gen ?? 0) + 1 : (mark?.__gen ?? 0));
139
+ const next = { ...clean, rev: floor + 1, gen };
140
+ const attributedPrincipal = (principal ?? this.anonymousAlias) ?? null;
141
+ this.writeMarkFile(this.markPathFor(sessionId, principal), next.rev, sessionId, attributedPrincipal, gen);
142
+ atomicWriteFile(join(this.dir, "tmp"), this.pathFor(sessionId, principal), JSON.stringify({ ...next, __sid: sessionId, __principal: attributedPrincipal }));
93
143
  return next;
94
144
  }
95
145
  async listBySession(sessionId) {
@@ -104,6 +154,8 @@ export class FileSessionPolicyStore {
104
154
  }
105
155
  const out = [];
106
156
  for (const file of files) {
157
+ if (file.endsWith(MARK_SUFFIX))
158
+ continue;
107
159
  if (!file.endsWith(".json"))
108
160
  continue;
109
161
  const full = join(this.dir, file);
@@ -149,6 +201,8 @@ export class FileSessionPolicyStore {
149
201
  throw err;
150
202
  }
151
203
  for (const file of files) {
204
+ if (file.endsWith(MARK_SUFFIX))
205
+ continue;
152
206
  if (!file.endsWith(".json"))
153
207
  continue;
154
208
  let raw;
@@ -163,6 +217,17 @@ export class FileSessionPolicyStore {
163
217
  parsed = JSON.parse(raw);
164
218
  }
165
219
  catch {
220
+ const tornMarkPath = join(this.dir, `${file.slice(0, -".json".length)}${MARK_SUFFIX}`);
221
+ try {
222
+ const tornMark = readSessionPolicyMark(tornMarkPath);
223
+ if (tornMark !== null && tornMark.__sid === sessionId) {
224
+ this.writeMarkFile(tornMarkPath, tornMark.__hwm, sessionId, tornMark.__principal ?? null, (tornMark.__gen ?? 0) + 1);
225
+ }
226
+ }
227
+ catch (err) {
228
+ if (!(err instanceof SessionPolicyError))
229
+ throw err;
230
+ }
166
231
  try {
167
232
  unlinkSync(join(this.dir, file));
168
233
  }
@@ -174,8 +239,23 @@ export class FileSessionPolicyStore {
174
239
  }
175
240
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed))
176
241
  continue;
177
- if (parsed.__sid !== sessionId)
242
+ const row = parsed;
243
+ if (row.__sid !== sessionId)
178
244
  continue;
245
+ const markPath = join(this.dir, `${file.slice(0, -".json".length)}${MARK_SUFFIX}`);
246
+ let mark;
247
+ try {
248
+ mark = readSessionPolicyMark(markPath);
249
+ }
250
+ catch (err) {
251
+ if (!(err instanceof SessionPolicyError))
252
+ throw err;
253
+ mark = undefined;
254
+ }
255
+ if (mark !== undefined) {
256
+ const rowRev = typeof row.rev === "number" && Number.isFinite(row.rev) ? row.rev : 0;
257
+ this.writeMarkFile(markPath, Math.max(mark?.__hwm ?? 0, rowRev), sessionId, row.__principal ?? null, (mark?.__gen ?? 0) + 1);
258
+ }
179
259
  try {
180
260
  unlinkSync(join(this.dir, file));
181
261
  }
@@ -9,7 +9,6 @@
9
9
  * does (an option skipped, a chain member missed) is how one face used to vouch for a program another refused.
10
10
  */
11
11
  import type { SegmentTokens } from "./bash-lexer.js";
12
- import type { AssertAllKeysHandled } from "../../core/ask-origin.js";
13
12
  /** The verdict every unresolvable / out-of-root / unbounded finding ends on. The findings differ in what
14
13
  * they found; the consequence is one consequence, and a copy that drifts reads as a second rule. */
15
14
  export declare const NOT_AUTO_ALLOWED = "\u2014 not auto-allowed";
@@ -169,35 +168,14 @@ export declare function readProgramPosition(toks: SegmentTokens): ProgramPositio
169
168
  * consumer; WHICH targets are accepted is the caller's declaration, not this reader's. */
170
169
  export declare function argvReentryUnjudged(pos: ProgramPosition, acceptedTargets: ReadonlySet<string>): boolean;
171
170
  /**
172
- * The two ways this reader DECLINES rather than judges — a CLOSED set, because which of them a seat reads as
173
- * evidence is a table each seat declares against (`fs-bash.ts`, where the argument is), not a boolean buried
174
- * in a caller. `unreadable_program_position`: the walk cannot name the program bash will run (`env -- ls`).
171
+ * The two ways this reader DECLINES rather than judges — a CLOSED set (`docs/CLOSED-SETS.md`), because what a
172
+ * seat does with a declined segment is a ROW of the shell read decision table (`shell-read-judge.ts`, the
173
+ * `declined` row: the one row the seats disagree on), not a boolean buried in a caller.
174
+ * `unreadable_program_position`: the walk cannot name the program bash will run (`env -- ls`).
175
175
  * `stream_fed_operands`: the operands arrive on a STREAM it never saw (`… | xargs cat`). Neither names a WORD.
176
176
  */
177
177
  export declare const SHELL_SCAN_DECLINES: readonly ["unreadable_program_position", "stream_fed_operands"];
178
178
  export type ShellScanDecline = (typeof SHELL_SCAN_DECLINES)[number];
179
- /** The two SEAT TABLES over that set — ROW PER MEMBER behind a compile-time fence, registered in
180
- * `docs/CLOSED-SETS.md` so `gate:closed-sets` is their runtime twin. The ARGUMENT for the rows is at the
181
- * seats that read them (`fs-bash.ts`), which DERIVE the `Set` shape their readers take. Rows and not two
182
- * sets because a derived set beside an empty literal made a third decline SILENTLY asymmetric, in the
183
- * widening direction; a member with no row now reds `tsc` at both fences with the word spelled out. */
184
- export declare const BOUNDARY_SEAT_READS_DECLINE: {
185
- readonly unreadable_program_position: true;
186
- readonly stream_fed_operands: true;
187
- };
188
- /** The fence over the read-boundary seat's table: `never` while every decline has a row. */
189
- export type BoundarySeatTableCoversEveryDecline = AssertAllKeysHandled<Exclude<ShellScanDecline, keyof typeof BOUNDARY_SEAT_READS_DECLINE>>;
190
- /** The classify doctrine's table — every row `false` BY THE ARGUMENT (stated at the seat), not by omission. */
191
- export declare const CLASSIFY_SEAT_READS_DECLINE: {
192
- readonly unreadable_program_position: false;
193
- readonly stream_fed_operands: false;
194
- };
195
- /** The fence over the classify seat's table. */
196
- export type ClassifySeatTableCoversEveryDecline = AssertAllKeysHandled<Exclude<ShellScanDecline, keyof typeof CLASSIFY_SEAT_READS_DECLINE>>;
197
- /** A seat's table in the shape its readers take — the members it answers YES for. DERIVED, so the ROWS are
198
- * the single source and a set can never say something its table does not. The parameter is TOTAL, not
199
- * `Partial`: that stops a third seat being written `seatDeclineEvidence({})`, one level up from the fences. */
200
- export declare function seatDeclineEvidence(table: Record<ShellScanDecline, boolean>): ReadonlySet<ShellScanDecline>;
201
179
  /** Does a SHELL stand where this segment's program would be — as the program itself (`sh -c`), or as a launcher
202
180
  * ROW handing its operands to one (`watch "…"`)? ONE predicate for the re-entry class, so the two spellings
203
181
  * cannot be answered differently; which names are shells is the caller's declaration. Not a decline. */
@@ -159,17 +159,6 @@ export function argvReentryUnjudged(pos, acceptedTargets) {
159
159
  return runs === undefined || !acceptedTargets.has(runs);
160
160
  }
161
161
  export const SHELL_SCAN_DECLINES = ["unreadable_program_position", "stream_fed_operands"];
162
- export const BOUNDARY_SEAT_READS_DECLINE = {
163
- unreadable_program_position: true,
164
- stream_fed_operands: true,
165
- };
166
- export const CLASSIFY_SEAT_READS_DECLINE = {
167
- unreadable_program_position: false,
168
- stream_fed_operands: false,
169
- };
170
- export function seatDeclineEvidence(table) {
171
- return new Set(SHELL_SCAN_DECLINES.filter((decline) => table[decline] === true));
172
- }
173
162
  export function programPositionRunsShell(pos, shellProgram) {
174
163
  if (pos.refusal !== undefined)
175
164
  return pos.refusal.kind === "control_syntax_at_program_position" && pos.refusal.runsShell === true;