@etiquekit/etq 1.0.14 → 1.0.16

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 (143) hide show
  1. package/AGENTS.md +26 -19
  2. package/LICENSE +3 -2
  3. package/NOTICE +4 -6
  4. package/QuickStart.md +50 -39
  5. package/README.md +56 -56
  6. package/bin/etiquette +5 -1
  7. package/bin/etiquette-core +5 -1
  8. package/docs/ARCHITECTURE.md +17 -20
  9. package/docs/CONCEPTS.md +5 -5
  10. package/docs/CORE_PROFILE.md +15 -7
  11. package/docs/LANE_PROVISIONING.md +2 -7
  12. package/docs/README.md +14 -13
  13. package/docs/RELEASE_SURFACE_AUDIT.md +7 -8
  14. package/docs/SEAT_DISCIPLINE.md +91 -55
  15. package/docs/SEAT_PROVISIONING.md +19 -16
  16. package/docs/TEAM_HANDOFF.md +21 -21
  17. package/docs/WORKTREE_QOL.md +0 -7
  18. package/docs/contracts/ledger-entry/README.md +11 -11
  19. package/docs/contracts/ledger-entry/ledger-entry.v0.2.md +2 -2
  20. package/docs/contracts/ledger-entry/ledger-entry.v0.md +8 -8
  21. package/lib/etiquette-core.js +313 -0
  22. package/lib/etiquette.js +1154 -0
  23. package/package.json +11 -10
  24. package/templates/etiquette-vanilla-v0/README.md +3 -2
  25. package/templates/etiquette-vanilla-v0/source/control-seat/README.md +6 -0
  26. package/templates/etiquette-vanilla-v0/source/control-seat/bin/access-assurance-check +55 -0
  27. package/templates/etiquette-vanilla-v0/source/control-seat/bin/seat-doctor +5 -0
  28. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/ACCESS-ASSURANCE.md +39 -0
  29. package/templates/etiquette-vanilla-v0/source/control-seat/docs/work/access/workspace-secure-profile.v0.json +53 -0
  30. package/templates/etiquette-vanilla-v0/validate-vanilla.sh +5 -0
  31. package/templates/hosted-receiver/README.md +41 -0
  32. package/templates/hosted-receiver/w1-github-webhook-receiver.mjs +129 -0
  33. package/templates/seat-packs-v0/README.md +2 -3
  34. package/templates/seat-packs-v0/source/claude-code-seat/README.md +3 -3
  35. package/templates/seat-packs-v0/source/codex-seat/README.md +3 -3
  36. package/templates/seat-packs-v0/source/gemini-seat/README.md +3 -3
  37. package/templates/seat-packs-v0/source/ollama-seat/README.md +3 -3
  38. package/templates/seat-packs-v0/source/openrouter-seat/README.md +3 -3
  39. package/docs/CONCEPT_STATUS.md +0 -66
  40. package/packages/control/src/authority/lease.ts +0 -261
  41. package/packages/control/src/authority/node-delegation.ts +0 -257
  42. package/packages/control/src/authority/rig-conductor.ts +0 -632
  43. package/packages/control/src/cli/argv.ts +0 -155
  44. package/packages/control/src/cli/commands/console.ts +0 -200
  45. package/packages/control/src/cli/commands/dispatch-core.ts +0 -89
  46. package/packages/control/src/cli/commands/dispatch.ts +0 -279
  47. package/packages/control/src/cli/commands/harness.ts +0 -89
  48. package/packages/control/src/cli/commands/hook.ts +0 -50
  49. package/packages/control/src/cli/commands/ledger.ts +0 -91
  50. package/packages/control/src/cli/commands/local-workflow.ts +0 -4690
  51. package/packages/control/src/cli/commands/memory.ts +0 -445
  52. package/packages/control/src/cli/commands/release.ts +0 -108
  53. package/packages/control/src/cli/commands/rubric.ts +0 -103
  54. package/packages/control/src/cli/commands/seat.ts +0 -179
  55. package/packages/control/src/cli/commands/session.ts +0 -127
  56. package/packages/control/src/cli/commands/supervision.ts +0 -246
  57. package/packages/control/src/cli/commands/sync.ts +0 -119
  58. package/packages/control/src/cli/commands/workflow.ts +0 -86
  59. package/packages/control/src/cli/commands/workspace.ts +0 -50
  60. package/packages/control/src/cli/core-usage.ts +0 -34
  61. package/packages/control/src/cli/prompt.ts +0 -67
  62. package/packages/control/src/cli/supervision-deps.ts +0 -44
  63. package/packages/control/src/cli/usage.ts +0 -241
  64. package/packages/control/src/cli.ts +0 -207
  65. package/packages/control/src/core-cli.ts +0 -50
  66. package/packages/control/src/dispatch/decision.ts +0 -202
  67. package/packages/control/src/dispatch/projection.ts +0 -293
  68. package/packages/control/src/dispatch/record.ts +0 -153
  69. package/packages/control/src/engagement/project.ts +0 -170
  70. package/packages/control/src/fs.ts +0 -19
  71. package/packages/control/src/harness/pruning.ts +0 -406
  72. package/packages/control/src/hooks/dispatcher.ts +0 -117
  73. package/packages/control/src/hooks/outbox.ts +0 -86
  74. package/packages/control/src/hooks/sanitize.ts +0 -6
  75. package/packages/control/src/hooks/types.ts +0 -34
  76. package/packages/control/src/index.ts +0 -384
  77. package/packages/control/src/ledger/entry.ts +0 -303
  78. package/packages/control/src/ledger/indexer.ts +0 -542
  79. package/packages/control/src/memory/context.ts +0 -149
  80. package/packages/control/src/memory/drain-import.ts +0 -207
  81. package/packages/control/src/memory/indexer.ts +0 -284
  82. package/packages/control/src/memory/query.ts +0 -75
  83. package/packages/control/src/memory/sanitize.ts +0 -50
  84. package/packages/control/src/memory/sharded-drain-import.ts +0 -212
  85. package/packages/control/src/memory/status.ts +0 -211
  86. package/packages/control/src/memory/store-lifecycle.ts +0 -509
  87. package/packages/control/src/memory/store.ts +0 -284
  88. package/packages/control/src/memory/types.ts +0 -146
  89. package/packages/control/src/parity/surfaces.ts +0 -748
  90. package/packages/control/src/project.ts +0 -141
  91. package/packages/control/src/projection/local-ledger-view.ts +0 -373
  92. package/packages/control/src/projection/return-enforcement.ts +0 -48
  93. package/packages/control/src/projection/timeline-preview.ts +0 -539
  94. package/packages/control/src/projection/timeline.ts +0 -708
  95. package/packages/control/src/release/readiness.ts +0 -842
  96. package/packages/control/src/rubric/loader.ts +0 -326
  97. package/packages/control/src/rubric/promotion.ts +0 -54
  98. package/packages/control/src/rubric/runner.ts +0 -159
  99. package/packages/control/src/rubric/types.ts +0 -158
  100. package/packages/control/src/seat/owner-card.ts +0 -388
  101. package/packages/control/src/seat/readiness.ts +0 -834
  102. package/packages/control/src/session/runbook.ts +0 -431
  103. package/packages/control/src/shared/sanitize.ts +0 -49
  104. package/packages/control/src/supervision/action-classes.ts +0 -192
  105. package/packages/control/src/supervision/command-apply.ts +0 -378
  106. package/packages/control/src/supervision/errors.ts +0 -14
  107. package/packages/control/src/supervision/event-replay.ts +0 -155
  108. package/packages/control/src/supervision/events.ts +0 -109
  109. package/packages/control/src/supervision/index.ts +0 -16
  110. package/packages/control/src/supervision/manifest.ts +0 -127
  111. package/packages/control/src/supervision/paths.ts +0 -49
  112. package/packages/control/src/supervision/projection-adapter.ts +0 -274
  113. package/packages/control/src/supervision/projection.ts +0 -75
  114. package/packages/control/src/supervision/rebuild.ts +0 -99
  115. package/packages/control/src/supervision/session-open.ts +0 -131
  116. package/packages/control/src/supervision/session-read.ts +0 -99
  117. package/packages/control/src/supervision/sqlite-impl.ts +0 -71
  118. package/packages/control/src/supervision/sqlite.ts +0 -121
  119. package/packages/control/src/supervision/store-rows.ts +0 -371
  120. package/packages/control/src/supervision/turn-close.ts +0 -154
  121. package/packages/control/src/supervision/turn-open.ts +0 -284
  122. package/packages/control/src/sync/event-log-merge-driver.ts +0 -263
  123. package/packages/control/src/sync/join-plan.ts +0 -375
  124. package/packages/control/src/sync/outbox.ts +0 -492
  125. package/packages/control/src/workflow/evaluator.ts +0 -140
  126. package/packages/control/src/workflow/loader.ts +0 -200
  127. package/packages/control/src/workflow/types.ts +0 -90
  128. package/packages/control/src/workspace/authority.ts +0 -499
  129. package/packages/protocol/src/guards.ts +0 -119
  130. package/packages/protocol/src/huddle-board.ts +0 -198
  131. package/packages/protocol/src/huddle.ts +0 -295
  132. package/packages/protocol/src/incident.ts +0 -251
  133. package/packages/protocol/src/index.ts +0 -8
  134. package/packages/protocol/src/interfaces.ts +0 -107
  135. package/packages/protocol/src/packet-profile.ts +0 -195
  136. package/packages/protocol/src/state.ts +0 -81
  137. package/packages/protocol/src/types.ts +0 -434
  138. package/release/lineage.v0.json +0 -15
  139. package/scripts/release-candidate-verify.sh +0 -175
  140. package/scripts/release-checksum.sh +0 -25
  141. package/scripts/release-pack-canary.sh +0 -97
  142. package/scripts/release-scan.sh +0 -249
  143. package/scripts/release-sign.sh +0 -34
@@ -1,384 +0,0 @@
1
- import { spawnSync } from 'node:child_process';
2
- import { mkdir, writeFile } from 'node:fs/promises';
3
- import { dirname, join, resolve } from 'node:path';
4
- import {
5
- aggregateSliceStatus,
6
- type ControlDeps,
7
- type ProjectManifest,
8
- type SecretarySummary,
9
- type Slice,
10
- type SliceTask,
11
- type Surface,
12
- } from '../../protocol/src/index';
13
- import { writeJson, now, sanitize } from './fs';
14
- import { buildSlice, loadSlice, resolveWorktreePath, sliceStatePath } from './project';
15
-
16
- export * from './authority/lease';
17
- export * from './authority/node-delegation';
18
- export * from './authority/rig-conductor';
19
- export {
20
- CODEX_PERMISSIONS_ADAPTER_SCHEMA,
21
- CODEX_PERMISSION_ACCESS,
22
- SEAT_PREFLIGHT_RESULT_SCHEMA,
23
- SEAT_PREFLIGHT_SCHEMA,
24
- SEAT_PREFLIGHT_REQUIRED_CHECKS,
25
- SEAT_PROFILE_ROLES,
26
- SEAT_PROFILE_SCHEMA,
27
- SEAT_MONITOR_MODES,
28
- codexFilesystemAccessForPath,
29
- createDefaultSeatProfile,
30
- evaluateSeatPreflight,
31
- normalizeCodexPermissionsAdapter,
32
- normalizeSeatProfile,
33
- type CodexFilesystemPermissionRule,
34
- type CodexNetworkPermissionRule,
35
- type CodexPermissionAccess,
36
- type CodexPermissionsAdapter,
37
- type SeatMonitorMode,
38
- type SeatPreflight,
39
- type SeatPreflightRequiredCheck,
40
- type SeatPreflightResult,
41
- type SeatProfile,
42
- type SeatProfileRole,
43
- } from './seat/readiness';
44
-
45
- async function persistSlice(project: ProjectManifest, slice: Slice): Promise<void> {
46
- slice.updatedAt = now();
47
- await writeJson(sliceStatePath(project, slice.sliceId), slice);
48
- }
49
-
50
- type CheckResult = {
51
- command: string;
52
- exitCode: number;
53
- stdout: string;
54
- stderr: string;
55
- };
56
-
57
- export async function startSlice(project: ProjectManifest, sliceId: string, deps: ControlDeps, surfaces: Surface[]): Promise<Slice> {
58
- await deps.handshake.ensureStore(project);
59
- const existing = await loadSlice(project, sliceId);
60
- if (existing) {
61
- await deps.scrutiny.openOrLinkSlice(project, existing);
62
- return existing;
63
- }
64
- const slice = buildSlice(project, sliceId, surfaces);
65
- await deps.scrutiny.openOrLinkSlice(project, slice);
66
- await persistSlice(project, slice);
67
- return slice;
68
- }
69
-
70
- export async function splitSlice(project: ProjectManifest, sliceId: string, deps: ControlDeps): Promise<Slice> {
71
- const slice = await loadSlice(project, sliceId);
72
- if (!slice) {
73
- throw new Error(`slice not started: ${sliceId}`);
74
- }
75
- for (const task of slice.tasks) {
76
- task.packetId = `slice-${sanitize(sliceId)}-${task.surface}`;
77
- await deps.handshake.newPacket({ packetId: task.packetId, sliceId, task, project });
78
- }
79
- slice.state = 'split';
80
- slice.transitions.push({ state: 'split', actorId: 'etiquette', at: now(), note: 'packets and incoming tasks created' });
81
- await persistSlice(project, slice);
82
- return slice;
83
- }
84
-
85
- export async function assignSlice(project: ProjectManifest, sliceId: string, deps: ControlDeps): Promise<Slice> {
86
- const slice = await loadSlice(project, sliceId);
87
- if (!slice) {
88
- throw new Error(`slice not started: ${sliceId}`);
89
- }
90
- for (const task of slice.tasks) {
91
- task.worktreePath = resolveWorktreePath(project, sliceId, task);
92
- task.branchName = `codex/${sanitize(sliceId)}-${task.surface}`;
93
- await deps.agent.spawnWorker(project, task);
94
- await deps.agent.bindWorktree(project, task, task.worktreePath, task.owner ?? 'unassigned');
95
- await deps.handshake.claimTask(project, task.taskId, task.owner ?? 'unassigned');
96
- task.state = 'claimed';
97
- task.transitions.push({ state: 'claimed', actorId: task.owner ?? 'unassigned', at: now(), note: `bound ${task.worktreePath}` });
98
- }
99
- slice.state = 'assigned';
100
- slice.transitions.push({ state: 'assigned', actorId: 'etiquette', at: now(), note: 'workers claimed bounded tasks' });
101
- await persistSlice(project, slice);
102
- return slice;
103
- }
104
-
105
- export async function statusSlice(project: ProjectManifest, sliceId: string, deps: ControlDeps) {
106
- const slice = await loadSlice(project, sliceId);
107
- if (!slice) {
108
- throw new Error(`slice not started: ${sliceId}`);
109
- }
110
- const scrutiny = await deps.scrutiny.readSliceState(project, slice.scrutinyId);
111
- const packetStates = await deps.handshake.readPacketStates(project);
112
- const ledgerTaskStates = await deps.handshake.readTaskStates(project);
113
- for (const task of slice.tasks) {
114
- const ledgerState = ledgerTaskStates[task.taskId];
115
- if (ledgerState) {
116
- task.state = ledgerState;
117
- }
118
- }
119
- const snapshot = aggregateSliceStatus(slice, scrutiny.state, packetStates);
120
- return { slice, scrutiny, snapshot };
121
- }
122
-
123
- function resolveTaskBySurface(slice: Slice, surface: Surface): SliceTask {
124
- const task = slice.tasks.find((candidate) => candidate.surface === surface);
125
- if (!task) {
126
- throw new Error(`slice ${slice.sliceId} missing task for surface ${surface}`);
127
- }
128
- return task;
129
- }
130
-
131
- function ensureTaskNotResolved(task: SliceTask): void {
132
- if (task.state === 'resolved') {
133
- throw new Error(`task ${task.taskId} is already resolved`);
134
- }
135
- }
136
-
137
- function runCheck(project: ProjectManifest, command: string): CheckResult {
138
- const result = spawnSync('zsh', ['-lc', command], {
139
- cwd: project.projectRoot,
140
- encoding: 'utf-8',
141
- });
142
- return {
143
- command,
144
- exitCode: result.status ?? 1,
145
- stdout: result.stdout ?? '',
146
- stderr: result.stderr ?? '',
147
- };
148
- }
149
-
150
- function renderEvidenceReport(task: SliceTask, actor: string, note: string, results: CheckResult[]): string {
151
- const lines = [
152
- `# Discipline Evidence: ${task.taskId}`,
153
- '',
154
- `- Surface: \`${task.surface}\``,
155
- `- Actor: \`${actor}\``,
156
- `- Summary: ${note}`,
157
- `- Captured At: ${now()}`,
158
- '',
159
- '## Checks',
160
- '',
161
- ];
162
- for (const result of results) {
163
- lines.push(`### \`${result.command}\``);
164
- lines.push('');
165
- lines.push(`- Exit code: \`${result.exitCode}\``);
166
- lines.push('');
167
- if (result.stdout.trim()) {
168
- lines.push('#### stdout');
169
- lines.push('');
170
- lines.push('```text');
171
- lines.push(result.stdout.trimEnd());
172
- lines.push('```');
173
- lines.push('');
174
- }
175
- if (result.stderr.trim()) {
176
- lines.push('#### stderr');
177
- lines.push('');
178
- lines.push('```text');
179
- lines.push(result.stderr.trimEnd());
180
- lines.push('```');
181
- lines.push('');
182
- }
183
- }
184
- return `${lines.join('\n')}\n`;
185
- }
186
-
187
- async function writeEvidenceFiles(project: ProjectManifest, task: SliceTask, actor: string, note: string, results: CheckResult[]): Promise<string[]> {
188
- const content = renderEvidenceReport(task, actor, note, results);
189
- const written: string[] = [];
190
- for (const relative of task.bounds.evidenceFiles) {
191
- const target = resolve(project.projectRoot, relative);
192
- await mkdir(dirname(target), { recursive: true });
193
- await writeFile(target, content, 'utf-8');
194
- written.push(relative);
195
- }
196
- return written;
197
- }
198
-
199
- export async function completeTask(
200
- project: ProjectManifest,
201
- sliceId: string,
202
- surface: Surface,
203
- deps: ControlDeps,
204
- actor: string,
205
- note: string,
206
- evidencePaths: string[] = [],
207
- ): Promise<SliceTask> {
208
- const slice = await loadSlice(project, sliceId);
209
- if (!slice) {
210
- throw new Error(`slice not started: ${sliceId}`);
211
- }
212
- const task = resolveTaskBySurface(slice, surface);
213
- ensureTaskNotResolved(task);
214
- if (!task.packetId) {
215
- throw new Error(`task ${task.taskId} has no packet`);
216
- }
217
- const files = evidencePaths.length > 0 ? evidencePaths : task.bounds.evidenceFiles;
218
- await deps.agent.captureReceipt(project, task.taskId, note, files);
219
- await deps.agent.reportTaskState(project, task.taskId, 'evidence_ready', note);
220
- await deps.handshake.resolveTask(project, task.taskId, actor, note);
221
- await deps.handshake.respondPacket(project, task.packetId, 'ACCEPTED');
222
- await deps.handshake.lockPacket(project, task.packetId);
223
- await deps.handshake.provisionPacket(project, task.packetId);
224
- await deps.agent.reportTaskState(project, task.taskId, 'resolved', note);
225
- task.state = 'resolved';
226
- task.transitions.push({
227
- state: 'resolved',
228
- actorId: actor,
229
- at: now(),
230
- note,
231
- });
232
- if (slice.tasks.every((candidate) => candidate.state === 'resolved')) {
233
- slice.state = 'validated';
234
- slice.transitions.push({
235
- state: 'validated',
236
- actorId: actor,
237
- at: now(),
238
- note: 'all bounded tasks resolved through discipline complete',
239
- });
240
- }
241
- await persistSlice(project, slice);
242
- return task;
243
- }
244
-
245
- export async function runTask(
246
- project: ProjectManifest,
247
- sliceId: string,
248
- surface: Surface,
249
- deps: ControlDeps,
250
- actor: string,
251
- note: string,
252
- ): Promise<SliceTask> {
253
- const slice = await loadSlice(project, sliceId);
254
- if (!slice) {
255
- throw new Error(`slice not started: ${sliceId}`);
256
- }
257
- const task = resolveTaskBySurface(slice, surface);
258
- ensureTaskNotResolved(task);
259
- await deps.agent.reportTaskState(project, task.taskId, 'in_progress', `running checks for ${task.taskId}`);
260
- const results: CheckResult[] = [];
261
- for (const command of task.bounds.checks) {
262
- const result = runCheck(project, command);
263
- results.push(result);
264
- if (result.exitCode !== 0) {
265
- const failureNote = `${note} (failed check: ${command})`;
266
- const evidence = await writeEvidenceFiles(project, task, actor, failureNote, results);
267
- await deps.agent.captureReceipt(project, task.taskId, failureNote, evidence);
268
- await deps.agent.reportTaskState(project, task.taskId, 'blocked', failureNote);
269
- throw new Error(result.stderr.trim() || result.stdout.trim() || `${command} failed`);
270
- }
271
- }
272
- const evidence = await writeEvidenceFiles(project, task, actor, note, results);
273
- return completeTask(project, sliceId, surface, deps, actor, note, evidence);
274
- }
275
-
276
- export async function secretarySummary(project: ProjectManifest, sliceId: string, deps: ControlDeps): Promise<SecretarySummary> {
277
- const { slice, snapshot } = await statusSlice(project, sliceId, deps);
278
- const actions: SecretarySummary['actions'] = [];
279
- for (const task of slice.tasks) {
280
- if (task.state === 'blocked' || task.state === 'countered') {
281
- actions.push({
282
- level: 'blocked',
283
- taskId: task.taskId,
284
- surface: task.surface,
285
- summary: `${task.taskId} needs escalation before work can continue`,
286
- });
287
- continue;
288
- }
289
- if (task.state === 'incoming') {
290
- actions.push({
291
- level: 'action',
292
- taskId: task.taskId,
293
- surface: task.surface,
294
- summary: `${task.taskId} has not been claimed`,
295
- });
296
- continue;
297
- }
298
- if (task.state === 'claimed') {
299
- actions.push({
300
- level: 'action',
301
- taskId: task.taskId,
302
- surface: task.surface,
303
- summary: `${task.taskId} is claimed and waiting for worker evidence`,
304
- });
305
- continue;
306
- }
307
- if (task.state === 'resolved') {
308
- actions.push({
309
- level: 'info',
310
- taskId: task.taskId,
311
- surface: task.surface,
312
- summary: `${task.taskId} is resolved and packet lifecycle is complete`,
313
- });
314
- }
315
- }
316
- if (snapshot.readyToClose) {
317
- actions.push({
318
- level: 'action',
319
- summary: `slice ${slice.sliceId} is ready to close`,
320
- });
321
- } else if (actions.length === 0) {
322
- actions.push({
323
- level: 'info',
324
- summary: `slice ${slice.sliceId} has no immediate secretary actions`,
325
- });
326
- }
327
- return { sliceId: slice.sliceId, actions };
328
- }
329
-
330
- export async function closeSlice(project: ProjectManifest, sliceId: string, deps: ControlDeps, renderArtifacts = false): Promise<Slice> {
331
- const { slice, snapshot } = await statusSlice(project, sliceId, deps);
332
- if (!snapshot.readyToClose) {
333
- throw new Error(`slice ${sliceId} is not ready to close: ${snapshot.blockers.join(', ') || snapshot.unresolvedTasks.join(', ')}`);
334
- }
335
- const contextId = await deps.logix.createDecisionContext(project, slice);
336
- await deps.logix.requestVerdict(project, slice, contextId);
337
- if (renderArtifacts) {
338
- await deps.scrutiny.renderArtifacts(project);
339
- }
340
- slice.state = 'closed';
341
- slice.transitions.push({ state: 'closed', actorId: 'etiquette', at: now(), note: `context=${contextId}` });
342
- await persistSlice(project, slice);
343
- return slice;
344
- }
345
-
346
- export function formatStatus(result: Awaited<ReturnType<typeof statusSlice>>): string {
347
- const { slice, scrutiny, snapshot } = result;
348
- const lines = [
349
- `slice=${slice.sliceId}`,
350
- `title=${slice.title}`,
351
- `slice_state=${slice.state}`,
352
- `scrutiny=${scrutiny.scrutinyId}:${scrutiny.state}`,
353
- `tasks=${slice.tasks.length}`,
354
- `ready_to_close=${snapshot.readyToClose}`,
355
- ];
356
- for (const [state, count] of Object.entries(snapshot.taskStates)) {
357
- if (count > 0) lines.push(`task_${state}=${count}`);
358
- }
359
- for (const [state, count] of Object.entries(snapshot.packetStates)) {
360
- if (count > 0) lines.push(`packet_${state}=${count}`);
361
- }
362
- if (scrutiny.kind) lines.push(`scrutiny_kind=${scrutiny.kind}`);
363
- if (scrutiny.blockedBy && scrutiny.blockedBy.length > 0) lines.push(`scrutiny_blocked_by=${scrutiny.blockedBy.join(',')}`);
364
- if (scrutiny.dependsOn && scrutiny.dependsOn.length > 0) lines.push(`scrutiny_depends_on=${scrutiny.dependsOn.join(',')}`);
365
- if (scrutiny.readinessQuestion) lines.push(`scrutiny_readiness_question=${scrutiny.readinessQuestion}`);
366
- if (scrutiny.readyForRevisit) lines.push(`scrutiny_ready_for_revisit=true`);
367
- if (snapshot.blockers.length > 0) {
368
- lines.push(`blockers=${snapshot.blockers.join(',')}`);
369
- }
370
- return lines.join('\n');
371
- }
372
-
373
- export function formatSecretarySummary(summary: SecretarySummary): string {
374
- const lines = [`slice=${summary.sliceId}`];
375
- for (const action of summary.actions) {
376
- const prefix = action.level.toUpperCase();
377
- if (action.taskId && action.surface) {
378
- lines.push(`${prefix} ${action.taskId} [${action.surface}] ${action.summary}`);
379
- } else {
380
- lines.push(`${prefix} ${action.summary}`);
381
- }
382
- }
383
- return lines.join('\n');
384
- }
@@ -1,303 +0,0 @@
1
- export const LEDGER_ENTRY_TYPES = [
2
- 'decision',
3
- 'review',
4
- 'receipt',
5
- 'audit',
6
- 'event',
7
- 'heartbeat',
8
- 'capability',
9
- 'incident',
10
- 'doctrine',
11
- 'task',
12
- 'closeout',
13
- 'retroactive_import',
14
- ] as const;
15
-
16
- export const LEDGER_AUTHORITY_CLASSES = [
17
- 'advisory',
18
- 'grant',
19
- 'receipt',
20
- 'audit',
21
- 'status',
22
- ] as const;
23
-
24
- export const LEDGER_EXPORT_CLASSES = [
25
- 'local_private',
26
- 'workspace_shared',
27
- 'org_projected',
28
- 'public_safe',
29
- ] as const;
30
-
31
- export const LEDGER_STATUS_EFFECTS = [
32
- 'none',
33
- 'observed',
34
- 'changed',
35
- 'gap_found',
36
- 'reserved',
37
- 'closed',
38
- 'proved',
39
- ] as const;
40
-
41
- export type LedgerEntryType = typeof LEDGER_ENTRY_TYPES[number];
42
- export type LedgerAuthorityClass = typeof LEDGER_AUTHORITY_CLASSES[number];
43
- export type LedgerExportClass = typeof LEDGER_EXPORT_CLASSES[number];
44
- export type LedgerStatusEffect = typeof LEDGER_STATUS_EFFECTS[number];
45
-
46
- export interface LedgerEntryRefs {
47
- lanes?: string[];
48
- commits?: string[];
49
- prs?: string[];
50
- bus?: string[];
51
- artifacts?: string[];
52
- receipts?: string[];
53
- }
54
-
55
- export interface LedgerEntryV0 {
56
- schema: 'ledger-entry.v0';
57
- entry_id: string;
58
- created_at: string;
59
- entry_type: LedgerEntryType;
60
- spine_eligible: boolean;
61
- authority: {
62
- class: LedgerAuthorityClass;
63
- grants_execution: boolean;
64
- };
65
- refs: LedgerEntryRefs;
66
- export_class: LedgerExportClass;
67
- summary: string;
68
- legacy?: {
69
- pre_v0?: boolean;
70
- low_authority?: boolean;
71
- };
72
- }
73
-
74
- export interface LedgerEntryV01 extends Omit<LedgerEntryV0, 'schema'> {
75
- schema: 'ledger-entry.v0.1';
76
- topic_hints?: string[];
77
- status_effect?: LedgerStatusEffect;
78
- }
79
-
80
- export interface LedgerEntryV02 extends Omit<LedgerEntryV01, 'schema'> {
81
- schema: 'ledger-entry.v0.2';
82
- valid_time?: string;
83
- valid_time_end?: string;
84
- }
85
-
86
- export type LedgerEntry = LedgerEntryV0 | LedgerEntryV01 | LedgerEntryV02;
87
-
88
- export type LedgerLineClassification =
89
- | { kind: 'ignored' }
90
- | { kind: 'legacy'; pre_v0: true; spine_eligible: false; low_authority: true }
91
- | { kind: 'v0'; pre_v0: false; spine_eligible: boolean };
92
-
93
- function assertIsoTimestamp(value: string): void {
94
- if (!Number.isFinite(Date.parse(value))) {
95
- throw new Error('ledger entry created_at must be an ISO-8601 timestamp');
96
- }
97
- }
98
-
99
- function assertOpaqueRef(value: string, field: string): void {
100
- if (!/^[A-Za-z0-9][A-Za-z0-9:._/@-]{0,255}$/.test(value)) {
101
- throw new Error(`${field} must be a compact opaque or repo-relative ref`);
102
- }
103
- }
104
-
105
- function assertSummary(value: string): void {
106
- if (value.length === 0 || value.length > 240 || /\s{2,}/.test(value)) {
107
- throw new Error('ledger entry summary must be compact and single-spaced');
108
- }
109
- }
110
-
111
- export function assertLedgerTopicHints(value: string[] | undefined): void {
112
- if (value === undefined) return;
113
- if (value.length > 8) {
114
- throw new Error('ledger entry topic_hints may contain at most 8 items');
115
- }
116
- const seen = new Set<string>();
117
- for (const topic of value) {
118
- if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(topic)) {
119
- throw new Error('ledger entry topic_hints must be kebab-case slugs');
120
- }
121
- if (seen.has(topic)) {
122
- throw new Error('ledger entry topic_hints must not contain duplicates');
123
- }
124
- seen.add(topic);
125
- }
126
- }
127
-
128
- function refsCount(refs: LedgerEntryRefs): number {
129
- return Object.values(refs).reduce((total, values) => total + (values?.length ?? 0), 0);
130
- }
131
-
132
- function compactRefs(refs: LedgerEntryRefs): string {
133
- const parts: string[] = [];
134
- for (const key of ['lanes', 'commits', 'prs', 'bus', 'artifacts', 'receipts'] as const) {
135
- for (const value of refs[key] ?? []) {
136
- assertOpaqueRef(value, `refs.${key}`);
137
- parts.push(`${key}:${value}`);
138
- }
139
- }
140
- return parts.join(',') || 'none';
141
- }
142
-
143
- function assertLedgerEntryBase(entry: LedgerEntry): void {
144
- assertIsoTimestamp(entry.created_at);
145
- assertOpaqueRef(entry.entry_id, 'entry_id');
146
- assertSummary(entry.summary);
147
-
148
- if (entry.authority.grants_execution && entry.authority.class !== 'grant') {
149
- throw new Error('only grant authority entries may set grants_execution=true');
150
- }
151
-
152
- if (entry.authority.class !== 'advisory' && refsCount(entry.refs) === 0) {
153
- throw new Error('non-advisory ledger entries require at least one durable ref');
154
- }
155
- }
156
-
157
- export function assertLedgerEntryV0(entry: LedgerEntryV0): void {
158
- assertLedgerEntryBase(entry);
159
- if (entry.entry_type === 'retroactive_import') {
160
- throw new Error('retroactive_import entries require schema ledger-entry.v0.2');
161
- }
162
- }
163
-
164
- export function assertLedgerEntryV01(entry: LedgerEntryV01): void {
165
- assertLedgerEntryBase(entry);
166
- assertLedgerTopicHints(entry.topic_hints);
167
- if (entry.status_effect !== undefined && !LEDGER_STATUS_EFFECTS.includes(entry.status_effect)) {
168
- throw new Error('ledger entry status_effect is not supported');
169
- }
170
- if (entry.entry_type === 'retroactive_import') {
171
- throw new Error('retroactive_import entries require schema ledger-entry.v0.2');
172
- }
173
- }
174
-
175
- export function assertLedgerEntryV02(entry: LedgerEntryV02): void {
176
- assertLedgerEntryBase(entry);
177
- assertLedgerTopicHints(entry.topic_hints);
178
- if (entry.status_effect !== undefined && !LEDGER_STATUS_EFFECTS.includes(entry.status_effect)) {
179
- throw new Error('ledger entry status_effect is not supported');
180
- }
181
-
182
- // Bitemporal honesty: created_at is the recording (transaction) time and is
183
- // never backdated; valid_time (+ optional end) is when the covered work
184
- // actually happened.
185
- if (entry.valid_time !== undefined) {
186
- if (!Number.isFinite(Date.parse(entry.valid_time))) {
187
- throw new Error('ledger entry valid_time must be an ISO-8601 timestamp');
188
- }
189
- if (Date.parse(entry.valid_time) > Date.parse(entry.created_at)) {
190
- throw new Error('ledger entry valid_time must not be after created_at (no future coverage, no backdated recording)');
191
- }
192
- }
193
- if (entry.valid_time_end !== undefined) {
194
- if (entry.valid_time === undefined) {
195
- throw new Error('ledger entry valid_time_end requires valid_time');
196
- }
197
- if (!Number.isFinite(Date.parse(entry.valid_time_end))) {
198
- throw new Error('ledger entry valid_time_end must be an ISO-8601 timestamp');
199
- }
200
- if (Date.parse(entry.valid_time_end) < Date.parse(entry.valid_time)) {
201
- throw new Error('ledger entry valid_time_end must not precede valid_time');
202
- }
203
- if (Date.parse(entry.valid_time_end) > Date.parse(entry.created_at)) {
204
- throw new Error('ledger entry valid_time_end must not be after created_at');
205
- }
206
- }
207
-
208
- if (entry.entry_type === 'retroactive_import') {
209
- if (entry.valid_time === undefined) {
210
- throw new Error('retroactive_import entries must declare valid_time');
211
- }
212
- const evidence =
213
- (entry.refs.commits?.length ?? 0) +
214
- (entry.refs.receipts?.length ?? 0) +
215
- (entry.refs.artifacts?.length ?? 0);
216
- if (evidence === 0) {
217
- throw new Error('retroactive_import entries require contemporaneous evidence refs (commits, receipts, or artifacts)');
218
- }
219
- if (entry.authority.class !== 'advisory' && entry.authority.class !== 'audit') {
220
- throw new Error('retroactive_import entries must carry advisory or audit authority; late entries never mint grants');
221
- }
222
- }
223
- }
224
-
225
- export function assertLedgerEntry(entry: LedgerEntry): void {
226
- if (entry.schema === 'ledger-entry.v0.2') {
227
- assertLedgerEntryV02(entry);
228
- return;
229
- }
230
- if (entry.schema === 'ledger-entry.v0.1') {
231
- assertLedgerEntryV01(entry);
232
- return;
233
- }
234
- assertLedgerEntryV0(entry);
235
- }
236
-
237
- export function normalizeLedgerEntryV01(entry: LedgerEntryV01): LedgerEntryV01 {
238
- assertLedgerEntryV01(entry);
239
- return {
240
- ...entry,
241
- topic_hints: entry.topic_hints ?? [],
242
- status_effect: entry.status_effect ?? 'none',
243
- };
244
- }
245
-
246
- export function formatLedgerEntryLine(entry: LedgerEntry, extra: Record<string, string> = {}): string {
247
- assertLedgerEntry(entry);
248
- const parts = [
249
- `- ${entry.created_at}`,
250
- `schema=${entry.schema}`,
251
- `entry_id=${entry.entry_id}`,
252
- `entry_type=${entry.entry_type}`,
253
- `spine_eligible=${entry.spine_eligible}`,
254
- `authority_class=${entry.authority.class}`,
255
- `grants_execution=${entry.authority.grants_execution}`,
256
- `export_class=${entry.export_class}`,
257
- `refs=${compactRefs(entry.refs)}`,
258
- ];
259
-
260
- if (entry.schema === 'ledger-entry.v0.1' || entry.schema === 'ledger-entry.v0.2') {
261
- const topicHints = entry.topic_hints ?? [];
262
- const statusEffect = entry.status_effect ?? 'none';
263
- if (topicHints.length > 0) {
264
- parts.push(`topic_hints=${topicHints.join(',')}`);
265
- }
266
- if (statusEffect !== 'none') {
267
- parts.push(`status_effect=${statusEffect}`);
268
- }
269
- }
270
-
271
- if (entry.schema === 'ledger-entry.v0.2') {
272
- if (entry.valid_time !== undefined) {
273
- parts.push(`valid_time=${entry.valid_time}`);
274
- }
275
- if (entry.valid_time_end !== undefined) {
276
- parts.push(`valid_time_end=${entry.valid_time_end}`);
277
- }
278
- }
279
-
280
- for (const [key, value] of Object.entries(extra)) {
281
- parts.push(`${key}=${value}`);
282
- }
283
-
284
- return parts.join(' ');
285
- }
286
-
287
- export function classifyLedgerLine(line: string): LedgerLineClassification {
288
- const trimmed = line.trim();
289
- if (trimmed.length === 0 || trimmed.startsWith('#')) {
290
- return { kind: 'ignored' };
291
- }
292
- if (!trimmed.startsWith('- ')) {
293
- return { kind: 'ignored' };
294
- }
295
- if (!/\bschema=ledger-entry\.v0(?:\.[12])?\b/.test(trimmed)) {
296
- return { kind: 'legacy', pre_v0: true, spine_eligible: false, low_authority: true };
297
- }
298
- return {
299
- kind: 'v0',
300
- pre_v0: false,
301
- spine_eligible: /\bspine_eligible=true\b/.test(trimmed),
302
- };
303
- }