@etiquekit/etq 1.0.13 → 1.0.15

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 +46 -35
  5. package/README.md +57 -55
  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 +6 -7
  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 +1151 -0
  23. package/package.json +13 -12
  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 -4686
  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 -831
  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 -14
  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,127 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
- import {
3
- ACTION_CLASSES,
4
- RUNTIME_KINDS,
5
- type ActionClass,
6
- type RuntimeKind,
7
- type SeatCapabilityManifest,
8
- } from '../../../protocol/src/index';
9
- import { resolveSeatManifestPath } from './paths';
10
-
11
- export class SeatManifestError extends Error {
12
- constructor(
13
- message: string,
14
- readonly path: string,
15
- readonly issues: string[],
16
- ) {
17
- super(`${message}: ${issues.join('; ')}`);
18
- this.name = 'SeatManifestError';
19
- }
20
- }
21
-
22
- function isString(v: unknown): v is string {
23
- return typeof v === 'string';
24
- }
25
-
26
- function isBoolean(v: unknown): v is boolean {
27
- return typeof v === 'boolean';
28
- }
29
-
30
- function isStringArray(v: unknown): v is string[] {
31
- return Array.isArray(v) && v.every(isString);
32
- }
33
-
34
- function isActionClass(v: unknown): v is ActionClass {
35
- return isString(v) && (ACTION_CLASSES as readonly string[]).includes(v);
36
- }
37
-
38
- function isRuntimeKind(v: unknown): v is RuntimeKind {
39
- return isString(v) && (RUNTIME_KINDS as readonly string[]).includes(v);
40
- }
41
-
42
- /**
43
- * Validate a parsed seat manifest object against the schema.
44
- * Returns the validated manifest or throws `SeatManifestError` with every issue.
45
- */
46
- export function validateSeatManifest(raw: unknown, path: string): SeatCapabilityManifest {
47
- const issues: string[] = [];
48
- if (typeof raw !== 'object' || raw === null || Array.isArray(raw)) {
49
- throw new SeatManifestError('manifest root is not an object', path, [
50
- `got ${Array.isArray(raw) ? 'array' : typeof raw}`,
51
- ]);
52
- }
53
- const obj = raw as Record<string, unknown>;
54
-
55
- const required: Array<[string, (v: unknown) => boolean, string]> = [
56
- ['schemaVersion', (v) => v === 'discipline-supervision-seat-capabilities.v1', 'must equal "discipline-supervision-seat-capabilities.v1"'],
57
- ['seatId', isString, 'must be a non-empty string'],
58
- ['displayName', isString, 'must be a string'],
59
- ['runtimeKind', isRuntimeKind, `must be one of: ${RUNTIME_KINDS.join(', ')}`],
60
- ['canReadHandshake', isBoolean, 'must be a boolean'],
61
- ['canWriteHandshake', isBoolean, 'must be a boolean'],
62
- ['canReadProductRepo', isBoolean, 'must be a boolean'],
63
- ['canRunBuilds', isBoolean, 'must be a boolean'],
64
- ['canWriteProductRepo', isBoolean, 'must be a boolean'],
65
- ['canLaunchRuntimeTargets', isBoolean, 'must be a boolean'],
66
- ['canAcceptMobileSteering', isBoolean, 'must be a boolean'],
67
- ['defaultRequiresHumanApprovalForProductMutation', isBoolean, 'must be a boolean'],
68
- ['updatedAt', isString, 'must be an ISO-8601 string'],
69
- ];
70
-
71
- for (const [key, predicate, msg] of required) {
72
- if (!(key in obj)) {
73
- issues.push(`${key} missing`);
74
- continue;
75
- }
76
- if (!predicate(obj[key])) {
77
- issues.push(`${key} invalid (${msg})`);
78
- }
79
- }
80
-
81
- if ('seatId' in obj && isString(obj.seatId) && obj.seatId.trim().length === 0) {
82
- issues.push('seatId must not be empty');
83
- }
84
-
85
- if (!('allowedActionClasses' in obj)) {
86
- issues.push('allowedActionClasses missing');
87
- } else if (!Array.isArray(obj.allowedActionClasses)) {
88
- issues.push('allowedActionClasses must be an array');
89
- } else {
90
- const bad = obj.allowedActionClasses.filter((v) => !isActionClass(v));
91
- if (bad.length > 0) {
92
- issues.push(`allowedActionClasses contains unknown values: ${bad.map(String).join(', ')}`);
93
- }
94
- }
95
-
96
- if (issues.length > 0) {
97
- throw new SeatManifestError('seat manifest validation failed', path, issues);
98
- }
99
-
100
- return obj as unknown as SeatCapabilityManifest;
101
- }
102
-
103
- /**
104
- * Read and validate a seat manifest from disk. The path defaults to the
105
- * user-global location (~/.local/share/etiquette/seat-capabilities.json)
106
- * unless overridden.
107
- */
108
- export async function loadSeatManifest(override?: string): Promise<SeatCapabilityManifest> {
109
- const path = resolveSeatManifestPath(override);
110
- let raw: string;
111
- try {
112
- raw = await readFile(path, 'utf-8');
113
- } catch (err) {
114
- throw new SeatManifestError(
115
- 'seat manifest could not be read',
116
- path,
117
- [(err as NodeJS.ErrnoException).code ?? String(err)],
118
- );
119
- }
120
- let parsed: unknown;
121
- try {
122
- parsed = JSON.parse(raw);
123
- } catch (err) {
124
- throw new SeatManifestError('seat manifest is not valid JSON', path, [String(err)]);
125
- }
126
- return validateSeatManifest(parsed, path);
127
- }
@@ -1,49 +0,0 @@
1
- import { homedir } from 'node:os';
2
- import { join, resolve } from 'node:path';
3
-
4
- export const SUPERVISION_RUNTIME_SUBDIR = '.etiquette/runtime/supervision';
5
-
6
- export interface SupervisionRuntimePaths {
7
- campaignRoot: string;
8
- runtimeDir: string;
9
- sqlitePath: string;
10
- eventsPath: string;
11
- latestStatePath: string;
12
- latestProjectionPath: string;
13
- }
14
-
15
- /**
16
- * Resolve campaign-local supervision runtime paths. Per doctrine
17
- * (llm-substrate/governance/shared-live-supervision.md), hot state lives at
18
- * <campaign-root>/.etiquette/runtime/supervision/.
19
- */
20
- export function supervisionRuntimePaths(campaignRoot: string): SupervisionRuntimePaths {
21
- const root = resolve(campaignRoot);
22
- const runtimeDir = join(root, SUPERVISION_RUNTIME_SUBDIR);
23
- return {
24
- campaignRoot: root,
25
- runtimeDir,
26
- sqlitePath: join(runtimeDir, 'supervision.sqlite'),
27
- eventsPath: join(runtimeDir, 'events.jsonl'),
28
- latestStatePath: join(runtimeDir, 'latest-state.json'),
29
- latestProjectionPath: join(runtimeDir, 'latest-projection.json'),
30
- };
31
- }
32
-
33
- export const DEFAULT_SEAT_MANIFEST_PATH = join(
34
- homedir(),
35
- '.local',
36
- 'share',
37
- 'etiquette',
38
- 'seat-capabilities.json',
39
- );
40
-
41
- /**
42
- * Resolve the seat capability manifest path, honoring an optional override.
43
- */
44
- export function resolveSeatManifestPath(override?: string): string {
45
- if (override && override.trim().length > 0) {
46
- return resolve(override);
47
- }
48
- return DEFAULT_SEAT_MANIFEST_PATH;
49
- }
@@ -1,274 +0,0 @@
1
- import { mkdir, writeFile } from 'node:fs/promises';
2
- import { dirname, join, resolve } from 'node:path';
3
- import type {
4
- SupervisionCommandRecord,
5
- SupervisionEvent,
6
- SupervisionProjection,
7
- SupervisionProjectionAdapter,
8
- SupervisionTurn,
9
- } from '../../../protocol/src/index';
10
- import { readAllSupervisionEvents } from './events';
11
-
12
- /**
13
- * File-system projection adapter for a campaign supervision session. This is
14
- * intentionally in control so the public package does not need the legacy
15
- * handshake adapter just to render local session projections.
16
- */
17
- export interface SupervisionProjectionConfig {
18
- projectionDir: string;
19
- eventsPath: string;
20
- seatsOfInterest?: string[];
21
- }
22
-
23
- export function createSupervisionProjectionAdapter(
24
- config: SupervisionProjectionConfig,
25
- ): SupervisionProjectionAdapter {
26
- const projectionDir = resolve(config.projectionDir);
27
- const eventsPath = resolve(config.eventsPath);
28
-
29
- const statusBoardPath = join(projectionDir, 'STATUS_BOARD.md');
30
- const channelPath = (seatId: string): string => {
31
- const sanitized = seatId.replace(/[^A-Za-z0-9._@-]/g, '_');
32
- return join(projectionDir, `${sanitized}.channel.md`);
33
- };
34
-
35
- return {
36
- async renderStatusBoard(projection: SupervisionProjection): Promise<void> {
37
- const md = renderStatusBoardMarkdown(projection);
38
- await mkdir(projectionDir, { recursive: true });
39
- await writeFile(statusBoardPath, md, 'utf-8');
40
- },
41
-
42
- async renderSeatChannel(projection: SupervisionProjection, seatId: string): Promise<void> {
43
- const events = await readAllSupervisionEvents(eventsPath);
44
- const md = renderSeatChannelMarkdown(projection, seatId, events);
45
- await mkdir(projectionDir, { recursive: true });
46
- await writeFile(channelPath(seatId), md, 'utf-8');
47
- },
48
-
49
- async writeLatestProjection(
50
- projectionPath: string,
51
- projection: SupervisionProjection,
52
- ): Promise<void> {
53
- const canonical = canonicalizeProjection(projection);
54
- await mkdir(dirname(projectionPath), { recursive: true });
55
- await writeFile(projectionPath, `${JSON.stringify(canonical, null, 2)}\n`, 'utf-8');
56
- },
57
- };
58
- }
59
-
60
- export function defaultSeatsFromProjection(projection: SupervisionProjection): string[] {
61
- const s = projection.sessionSnapshot;
62
- return [...new Set([s.leadSeat, s.responderSeat, s.escalationOwner])];
63
- }
64
-
65
- function renderStatusBoardMarkdown(projection: SupervisionProjection): string {
66
- const s = projection.sessionSnapshot;
67
- const lines: string[] = [];
68
- lines.push('# Supervision Status Board');
69
- lines.push('');
70
- lines.push('> Generated by etiquette supervision sidecar.');
71
- lines.push('> Source of truth: `events.jsonl` in the campaign runtime.');
72
- lines.push('> If this file and supervision state disagree, regenerate; do not hand-edit.');
73
- lines.push('');
74
- lines.push('## Session');
75
- lines.push('');
76
- lines.push(`- **session_id:** \`${s.sessionId}\``);
77
- lines.push(`- **topic:** ${s.topic}`);
78
- lines.push(`- **objective:** ${s.objective}`);
79
- lines.push(`- **status:** \`${s.status}\``);
80
- lines.push(`- **lead_seat:** \`${s.leadSeat}\``);
81
- lines.push(`- **responder_seat:** \`${s.responderSeat}\``);
82
- lines.push(`- **escalation_owner:** \`${s.escalationOwner}\``);
83
- lines.push(`- **time_cap_minutes:** ${s.timeCapMinutes}`);
84
- lines.push(`- **turn_cap:** ${s.turnCap}`);
85
- if (s.nextOwnerHint) {
86
- lines.push(`- **next_owner_hint:** \`${s.nextOwnerHint}\``);
87
- }
88
- if (s.approvedNextTurnJson) {
89
- lines.push('- **approved_next_turn:** present (see events.jsonl for payload)');
90
- }
91
- lines.push(`- **created_at:** ${s.createdAt}`);
92
- lines.push(`- **updated_at:** ${s.updatedAt}`);
93
- if (s.sourceRefs.length > 0) {
94
- lines.push('- **source_refs:**');
95
- for (const ref of [...s.sourceRefs].sort()) {
96
- lines.push(` - ${ref}`);
97
- }
98
- }
99
- lines.push('');
100
- lines.push('## Current turn');
101
- lines.push('');
102
- if (projection.currentTurn) {
103
- renderTurn(lines, projection.currentTurn);
104
- } else {
105
- lines.push('_no open turn_');
106
- }
107
- lines.push('');
108
- lines.push('## Pending commands');
109
- lines.push('');
110
- if (projection.pendingCommands.length === 0) {
111
- lines.push('_none_');
112
- } else {
113
- for (const c of [...projection.pendingCommands].sort((a, b) => a.issuedAt.localeCompare(b.issuedAt))) {
114
- renderCommand(lines, c);
115
- }
116
- }
117
- if (s.lastBlockerJson) {
118
- lines.push('');
119
- lines.push('## Last blocker');
120
- lines.push('');
121
- lines.push('```json');
122
- lines.push(formatJson(s.lastBlockerJson));
123
- lines.push('```');
124
- }
125
- lines.push('');
126
- lines.push('---');
127
- lines.push(`Rendered at ${projection.renderedAt} by the supervision sidecar.`);
128
- lines.push('Divergence rule: patch this file by regenerating, not by hand-edit.');
129
- lines.push('');
130
- return lines.join('\n');
131
- }
132
-
133
- function renderTurn(lines: string[], turn: SupervisionTurn): void {
134
- lines.push(`- **turn_id:** \`${turn.turnId}\``);
135
- lines.push(`- **sequence_no:** ${turn.sequenceNo}`);
136
- lines.push(`- **status:** \`${turn.status}\``);
137
- lines.push(`- **owner:** \`${turn.ownerSeat}\``);
138
- lines.push(`- **action_class:** \`${turn.allowedActionClass}\``);
139
- lines.push(`- **max_duration_minutes:** ${turn.maxDurationMinutes}`);
140
- lines.push(`- **allowed_commands:** [${[...turn.allowedCommands].sort().join(', ')}]`);
141
- if (turn.repoRoots.length > 0) {
142
- lines.push(`- **repo_roots:** [${[...turn.repoRoots].sort().join(', ')}]`);
143
- }
144
- if (turn.ownedPaths.length > 0) {
145
- lines.push(`- **owned_paths:** [${[...turn.ownedPaths].sort().join(', ')}]`);
146
- }
147
- if (turn.forbiddenPaths.length > 0) {
148
- lines.push(`- **forbidden_paths:** [${[...turn.forbiddenPaths].sort().join(', ')}]`);
149
- }
150
- if (turn.verifyCommands.length > 0) {
151
- lines.push(`- **verify_commands:** [${[...turn.verifyCommands].sort().join(', ')}]`);
152
- }
153
- lines.push(`- **requires_human_approval:** ${turn.requiresHumanApproval}`);
154
- lines.push(`- **projection_policy:** \`${turn.projectionPolicy}\``);
155
- lines.push(`- **started_at:** ${turn.startedAt}`);
156
- }
157
-
158
- function renderCommand(lines: string[], command: SupervisionCommandRecord): void {
159
- lines.push(`- \`${command.command}\` by \`${command.issuedBy}\` at ${command.issuedAt}`);
160
- }
161
-
162
- function renderSeatChannelMarkdown(
163
- projection: SupervisionProjection,
164
- seatId: string,
165
- events: SupervisionEvent[],
166
- ): string {
167
- const s = projection.sessionSnapshot;
168
- const seatAudience = `seat:${seatId}` as const;
169
- const roles: string[] = [];
170
- if (s.leadSeat === seatId) roles.push('lead');
171
- if (s.responderSeat === seatId) roles.push('responder');
172
- if (s.escalationOwner === seatId) roles.push('escalation_owner');
173
-
174
- const metaEventTypes = new Set(['projection.rendered', 'rebuild.completed']);
175
- const filtered = events.filter((e) => {
176
- if (metaEventTypes.has(e.eventType)) return false;
177
- if (e.audience === 'all') return true;
178
- if (e.audience === seatAudience) return true;
179
- if (e.audience === 'lead' && roles.includes('lead')) return true;
180
- if (e.audience === 'responder' && roles.includes('responder')) return true;
181
- if (e.audience === 'escalation_owner' && roles.includes('escalation_owner')) return true;
182
- return false;
183
- });
184
-
185
- const lines: string[] = [];
186
- lines.push(`# ${seatId} - Supervision Channel`);
187
- lines.push('');
188
- lines.push(`- **session_id:** \`${s.sessionId}\``);
189
- lines.push(`- **session_topic:** ${s.topic}`);
190
- lines.push(`- **seat_roles_in_session:** ${roles.length > 0 ? roles.join(', ') : '_none_'}`);
191
- lines.push(`- **generated_at:** ${projection.renderedAt}`);
192
- lines.push('');
193
- lines.push('> Auto-generated projection. Do not hand-edit.');
194
- lines.push('> Authoritative source: `events.jsonl` filtered by seat audience.');
195
- lines.push('> Regeneration overwrites this file on every state change.');
196
- lines.push('');
197
- lines.push(`## Event log (${filtered.length} entries)`);
198
- lines.push('');
199
- if (filtered.length === 0) {
200
- lines.push('_no events addressed to this seat yet_');
201
- } else {
202
- for (const e of filtered) {
203
- lines.push(`- \`${e.at}\` — \`${e.eventType}\` — ${formatEventSummary(e)}`);
204
- }
205
- }
206
- lines.push('');
207
- return lines.join('\n');
208
- }
209
-
210
- function formatEventSummary(event: SupervisionEvent): string {
211
- switch (event.eventType) {
212
- case 'session.opened':
213
- return `session opened by ${event.actor}`;
214
- case 'session.status_changed': {
215
- const p = (event.payload as { from?: string; to?: string }) ?? {};
216
- return `status ${p.from ?? '?'} -> ${p.to ?? '?'}`;
217
- }
218
- case 'session.next_owner_changed': {
219
- const p = (event.payload as { from?: string | null; to?: string | null }) ?? {};
220
- return `next_owner_hint ${p.from ?? 'null'} -> ${p.to ?? 'null'}`;
221
- }
222
- case 'turn.opened': {
223
- const p = (event.payload as { ownerSeat?: string; allowedActionClass?: string }) ?? {};
224
- return `turn opened for ${p.ownerSeat ?? '?'} (${p.allowedActionClass ?? '?'})`;
225
- }
226
- case 'turn.closed': {
227
- const p = (event.payload as { result?: string; summary?: string }) ?? {};
228
- return `turn closed (${p.result ?? '?'}): ${p.summary ?? ''}`;
229
- }
230
- case 'command.queued': {
231
- const p = (event.payload as { command?: string; issuedBy?: string }) ?? {};
232
- return `${p.command ?? '?'} queued by ${p.issuedBy ?? '?'}`;
233
- }
234
- case 'command.applied': {
235
- const p = (event.payload as { command?: string }) ?? {};
236
- return `${p.command ?? '?'} applied`;
237
- }
238
- case 'command.rejected': {
239
- const p = (event.payload as { command?: string; reason?: string }) ?? {};
240
- return `${p.command ?? '?'} rejected: ${p.reason ?? ''}`;
241
- }
242
- case 'projection.rendered':
243
- return 'projection rendered';
244
- case 'rebuild.completed': {
245
- const p = (event.payload as { eventCount?: number }) ?? {};
246
- return `rebuild completed (${p.eventCount ?? '?'} events replayed)`;
247
- }
248
- default:
249
- return `(event type ${String(event.eventType)})`;
250
- }
251
- }
252
-
253
- function formatJson(raw: string): string {
254
- try {
255
- return JSON.stringify(JSON.parse(raw), null, 2);
256
- } catch {
257
- return raw;
258
- }
259
- }
260
-
261
- function canonicalizeProjection(projection: SupervisionProjection): SupervisionProjection {
262
- const sortedSourceRefs = [...projection.sessionSnapshot.sourceRefs].sort();
263
- const sortedPendingCommands = [...projection.pendingCommands].sort((a, b) =>
264
- a.issuedAt.localeCompare(b.issuedAt) || a.commandId.localeCompare(b.commandId),
265
- );
266
- return {
267
- ...projection,
268
- sessionSnapshot: {
269
- ...projection.sessionSnapshot,
270
- sourceRefs: sortedSourceRefs,
271
- },
272
- pendingCommands: sortedPendingCommands,
273
- };
274
- }
@@ -1,75 +0,0 @@
1
- import { mkdir, writeFile } from 'node:fs/promises';
2
- import { join } from 'node:path';
3
- import type {
4
- SupervisionCommandRecord,
5
- SupervisionDeps,
6
- SupervisionProjection,
7
- SupervisionSession,
8
- SupervisionTurn,
9
- } from '../../../protocol/src/index';
10
- import type { supervisionRuntimePaths } from './paths';
11
-
12
- /**
13
- * Projection coordination — internal helpers shared by every runtime writer.
14
- *
15
- * These were previously inline in `session.ts`. Extracted per the Phase D.7
16
- * surgical split so the public-entrypoint modules (session-open, turn-open,
17
- * turn-close, command-apply, rebuild, session-read) can import a single
18
- * shared implementation instead of duplicating coordination logic.
19
- *
20
- * No public export surface — callers remain the runtime modules within this
21
- * directory. Not re-exported from `supervision/index.ts`.
22
- */
23
-
24
- /**
25
- * Write `{ session, currentTurn }` to disk as pretty-printed JSON.
26
- *
27
- * Used after every state-mutating runtime operation so the latest-state.json
28
- * artifact stays in sync with SQLite.
29
- */
30
- export async function writeLatestState(
31
- path: string,
32
- session: SupervisionSession,
33
- currentTurn: SupervisionTurn | null,
34
- ): Promise<void> {
35
- const body = JSON.stringify({ session, currentTurn }, null, 2);
36
- await mkdir(join(path, '..'), { recursive: true });
37
- await writeFile(path, `${body}\n`, 'utf-8');
38
- }
39
-
40
- /**
41
- * Build a `SupervisionProjection` snapshot and drive the configured
42
- * projection adapter through its three render hooks:
43
- * - `writeLatestProjection` (machine-readable snapshot)
44
- * - `renderStatusBoard` (single STATUS_BOARD.md for the campaign)
45
- * - `renderSeatChannel` for each of lead / responder / escalation_owner
46
- * (de-duplicated; a seat can play multiple roles in the same session)
47
- *
48
- * Per PHASE1.md § Projection rules, this runs on every state change and is
49
- * idempotent — safe to rerun after partial failure.
50
- */
51
- export async function renderProjection(
52
- deps: SupervisionDeps,
53
- paths: ReturnType<typeof supervisionRuntimePaths>,
54
- session: SupervisionSession,
55
- currentTurn: SupervisionTurn | null,
56
- pendingCommands: SupervisionCommandRecord[],
57
- ): Promise<SupervisionProjection> {
58
- const projection: SupervisionProjection = {
59
- sessionId: session.sessionId,
60
- renderedAt: deps.now().toISOString(),
61
- sessionSnapshot: session,
62
- currentTurn,
63
- pendingCommands,
64
- };
65
-
66
- await deps.projectionAdapter.writeLatestProjection(paths.latestProjectionPath, projection);
67
- await deps.projectionAdapter.renderStatusBoard(projection);
68
-
69
- const seats = new Set([session.leadSeat, session.responderSeat, session.escalationOwner]);
70
- for (const seatId of seats) {
71
- await deps.projectionAdapter.renderSeatChannel(projection, seatId);
72
- }
73
-
74
- return projection;
75
- }
@@ -1,99 +0,0 @@
1
- import { existsSync } from 'node:fs';
2
- import { rm } from 'node:fs/promises';
3
- import type {
4
- SupervisionDeps,
5
- SupervisionProjection,
6
- } from '../../../protocol/src/index';
7
- import {
8
- appendSupervisionEvent,
9
- buildEvent,
10
- readAllSupervisionEvents,
11
- } from './events';
12
- import { replayEvent } from './event-replay';
13
- import { supervisionRuntimePaths } from './paths';
14
- import { renderProjection, writeLatestState } from './projection';
15
- import { applySupervisionSchema } from './sqlite';
16
- import { withSupervisionTransaction } from './sqlite-impl';
17
- import {
18
- insertEventRow,
19
- loadCurrentTurn,
20
- loadPendingCommands,
21
- loadSession,
22
- } from './store-rows';
23
-
24
- export interface SessionRebuildArgs {
25
- sessionId: string;
26
- campaignRoot: string;
27
- }
28
-
29
- /**
30
- * Drop the SQLite mirror (including WAL + SHM siblings), then replay
31
- * `events.jsonl` into a fresh schema. All replays for the session run
32
- * inside a single transaction so a partial rebuild cannot leave a
33
- * half-populated mirror visible to concurrent readers.
34
- *
35
- * Final step: append a `rebuild.completed` event so history remains
36
- * self-describing.
37
- *
38
- * Per PHASE1.md § Session-rebuild contract: after this call, SQLite state
39
- * MUST equal the pre-rebuild state for any durable session.
40
- */
41
- export async function rebuildSupervisionSession(
42
- args: SessionRebuildArgs,
43
- deps: SupervisionDeps,
44
- ): Promise<SupervisionProjection> {
45
- const paths = supervisionRuntimePaths(args.campaignRoot);
46
-
47
- if (existsSync(paths.sqlitePath)) {
48
- await rm(paths.sqlitePath, { force: true });
49
- }
50
- // Remove WAL + SHM siblings too.
51
- await rm(`${paths.sqlitePath}-wal`, { force: true });
52
- await rm(`${paths.sqlitePath}-shm`, { force: true });
53
-
54
- const events = await readAllSupervisionEvents(paths.eventsPath);
55
- const db = deps.openSqlite(paths.sqlitePath);
56
- try {
57
- applySupervisionSchema(db);
58
-
59
- // Replay is idempotent per event but inserts many rows; group the whole
60
- // replay as one atomic bundle so a partial rebuild cannot leave a
61
- // half-populated SQLite mirror visible to concurrent readers.
62
- const eventCount = withSupervisionTransaction(db, () => {
63
- let count = 0;
64
- for (const event of events) {
65
- if (event.sessionId !== args.sessionId) continue;
66
- replayEvent(db, event);
67
- count += 1;
68
- }
69
- return count;
70
- });
71
-
72
- const session = loadSession(db, args.sessionId);
73
- const currentTurn = loadCurrentTurn(db, session);
74
- const pending = loadPendingCommands(db, args.sessionId);
75
-
76
- await writeLatestState(paths.latestStatePath, session, currentTurn);
77
- const projection = await renderProjection(deps, paths, session, currentTurn, pending);
78
-
79
- const completedAt = deps.now().toISOString();
80
- const completed = buildEvent(deps, {
81
- eventType: 'rebuild.completed',
82
- sessionId: args.sessionId,
83
- turnId: session.currentTurnId,
84
- commandId: null,
85
- actor: 'system',
86
- audience: 'all',
87
- at: completedAt,
88
- payload: { completedAt, eventCount },
89
- });
90
- await appendSupervisionEvent(paths.eventsPath, completed);
91
- withSupervisionTransaction(db, () => {
92
- insertEventRow(db, completed);
93
- });
94
-
95
- return projection;
96
- } finally {
97
- db.close();
98
- }
99
- }