@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,492 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
3
- import { existsSync } from 'node:fs';
4
- import { dirname, join, relative, resolve } from 'node:path';
5
- import { parse as parseYaml } from 'yaml';
6
- import { assertSharedMemoryScopeId, assertSharedMemoryText, detectMemoryRiskFlags } from '../memory/sanitize';
7
- import { defaultSeatReadinessEventsPath, defaultSeatReadinessPath, type SeatReadinessRecord } from '../seat/readiness';
8
-
9
- export const SYNC_ARTIFACT_KINDS = [
10
- 'ledger_entry',
11
- 'event_summary',
12
- 'receipt_metadata',
13
- 'dispatch_record',
14
- 'readiness_snapshot',
15
- 'memory_capsule',
16
- 'playback_summary',
17
- 'grant_ref',
18
- 'audit_ref',
19
- 'compaction_capsule',
20
- ] as const;
21
-
22
- export type SyncArtifactKind = (typeof SYNC_ARTIFACT_KINDS)[number];
23
- export type SyncScope = 'seat_local' | 'workspace_local' | 'org_projected' | 'subscriber_targeted';
24
-
25
- export interface SyncEnvelope {
26
- schema: 'sync-envelope.v0';
27
- envelope_id: string;
28
- created_at: string;
29
- source: {
30
- scope: 'seat_local' | 'workspace_local';
31
- org_id?: string;
32
- workspace_id: string;
33
- seat_id?: string;
34
- session_id?: string;
35
- };
36
- target: {
37
- scope: 'workspace_local' | 'org_projected' | 'subscriber_targeted';
38
- org_id?: string;
39
- workspace_id?: string;
40
- subscriber_refs: string[];
41
- };
42
- artifact: {
43
- kind: SyncArtifactKind;
44
- schema_ref: string;
45
- artifact_ref: string;
46
- summary: string;
47
- evidence_refs: Array<{
48
- type: 'ledger' | 'receipt' | 'event' | 'memory' | 'playback' | 'grant' | 'audit' | 'doc';
49
- ref: string;
50
- }>;
51
- };
52
- projection_policy_ref: string;
53
- visibility: 'workspace_local' | 'org_projected' | 'subscriber_targeted';
54
- content_boundary: {
55
- metadata_only: true;
56
- raw_ledger_body_included: false;
57
- raw_event_body_included: false;
58
- raw_prompt_included: false;
59
- raw_tool_output_included: false;
60
- source_snippets_included: false;
61
- local_paths_included: false;
62
- };
63
- authority_boundary: {
64
- can_authorize_work: false;
65
- can_execute_work: false;
66
- can_attach_grant: false;
67
- can_replace_local_ledger: false;
68
- remote_is_execution_truth: false;
69
- };
70
- }
71
-
72
- export interface SyncStageIssue {
73
- artifact_ref: string;
74
- kind: SyncArtifactKind | 'unknown';
75
- reason: string;
76
- }
77
-
78
- export interface SyncStageResult {
79
- schema: 'sync-stage-result.v0';
80
- project_ref: string;
81
- outbox_ref: string;
82
- staged: number;
83
- rejected: SyncStageIssue[];
84
- local_only: SyncStageIssue[];
85
- envelopes: Array<{
86
- envelope_id: string;
87
- kind: SyncArtifactKind;
88
- artifact_ref: string;
89
- outbox_ref: string;
90
- target_scope: SyncEnvelope['target']['scope'];
91
- projection_policy_ref: string;
92
- }>;
93
- authority_boundary: {
94
- can_authorize_work: false;
95
- can_execute_work: false;
96
- can_attach_grant: false;
97
- can_replace_local_ledger: false;
98
- hosted_plane_is_source_of_truth: false;
99
- };
100
- }
101
-
102
- interface Candidate {
103
- kind: SyncArtifactKind;
104
- sourceScope: 'seat_local' | 'workspace_local';
105
- targetScope: 'workspace_local' | 'org_projected' | 'subscriber_targeted';
106
- projectionPolicyRef: string;
107
- schemaRef: string;
108
- artifactRef: string;
109
- summary: string;
110
- evidenceType: SyncEnvelope['artifact']['evidence_refs'][number]['type'];
111
- rawForPolicy?: string;
112
- seatId?: string;
113
- }
114
-
115
- export interface StageSyncOutboxOptions {
116
- projectRoot: string;
117
- outboxDir?: string;
118
- workspaceId?: string;
119
- orgId?: string;
120
- seatId?: string;
121
- at?: string;
122
- }
123
-
124
- const DEFAULT_WORKSPACE_ID = 'workspace-local';
125
- const DEFAULT_OUTBOX_REF = '.etiquette/outbox/sync';
126
-
127
- function isoNow(at?: string): string {
128
- const value = at ?? new Date().toISOString();
129
- if (!Number.isFinite(Date.parse(value))) {
130
- throw new Error('--at must be an ISO-8601 timestamp');
131
- }
132
- return new Date(value).toISOString();
133
- }
134
-
135
- function stableHash(value: unknown): string {
136
- return createHash('sha256').update(JSON.stringify(value)).digest('hex').slice(0, 16);
137
- }
138
-
139
- function assertNoRestrictedContent(value: string, field: string): void {
140
- const flags: string[] = detectMemoryRiskFlags(value).filter((flag) => flag !== 'contains_credential');
141
- if (/\b(?:ANTHROPIC_API_KEY|OPENAI_API_KEY|GEMINI_API_KEY|api[_-]?key|token|secret|credential)\b\s*[:=]/i.test(value)
142
- || /(?:^|[\s:=])sk-[A-Za-z0-9_-]{12,}/.test(value)) {
143
- flags.push('contains_credential');
144
- }
145
- if (flags.length > 0) {
146
- throw new Error(`${field} contains restricted shared-sync content: ${flags.join(', ')}`);
147
- }
148
- }
149
-
150
- function assertNoRestrictedRefContent(value: string, field: string): void {
151
- const flags: string[] = detectMemoryRiskFlags(value).filter((flag) => flag !== 'contains_credential');
152
- if (/(?:^|[/:=_-])(?:api[_-]?key|token|secret|credential)(?:[/:=_-]|$)/i.test(value)
153
- || /(?:^|[/:=_-])sk-[A-Za-z0-9_-]{12,}/.test(value)) {
154
- flags.push('contains_credential');
155
- }
156
- if (flags.length > 0) {
157
- throw new Error(`${field} contains restricted shared-sync content: ${flags.join(', ')}`);
158
- }
159
- }
160
-
161
- function assertRepoRelative(value: string, field: string): string {
162
- if (value.length === 0 || value.length > 256) {
163
- throw new Error(`${field} must be a non-empty repo-relative ref <= 256 chars`);
164
- }
165
- if (value.startsWith('/') || value.startsWith('\\') || /^[A-Za-z]:[\\/]/.test(value)) {
166
- throw new Error(`${field} must be repo-relative`);
167
- }
168
- assertNoRestrictedRefContent(value, field);
169
- return value;
170
- }
171
-
172
- function assertSummary(value: string, field: string): string {
173
- const summary = value.trim().replace(/\s+/g, ' ').slice(0, 200);
174
- if (!summary) throw new Error(`${field} must not be empty`);
175
- assertSharedMemoryText(summary, field);
176
- return summary;
177
- }
178
-
179
- function projectRelative(projectRoot: string, path: string): string {
180
- const rel = relative(resolve(projectRoot), resolve(path));
181
- if (rel.length === 0 || rel.startsWith('..') || rel.includes('\\')) {
182
- throw new Error(`path must stay inside project root: ${path}`);
183
- }
184
- return assertRepoRelative(rel, 'artifact_ref');
185
- }
186
-
187
- function outboxRelative(projectRoot: string, path: string): string {
188
- const rel = relative(resolve(projectRoot), resolve(path));
189
- if (rel.length === 0 || rel.startsWith('..') || rel.includes('\\')) {
190
- throw new Error(`outbox path must stay inside project root: ${path}`);
191
- }
192
- return assertRepoRelative(rel, 'outbox_ref');
193
- }
194
-
195
- function resolveOutboxDir(projectRoot: string, outboxDir?: string): string {
196
- const outbox = resolve(projectRoot, outboxDir ?? DEFAULT_OUTBOX_REF);
197
- outboxRelative(projectRoot, outbox);
198
- return outbox;
199
- }
200
-
201
- async function readTextIfExists(path: string): Promise<string | null> {
202
- try {
203
- return await readFile(path, 'utf-8');
204
- } catch (err) {
205
- if ((err as NodeJS.ErrnoException).code === 'ENOENT') return null;
206
- throw err;
207
- }
208
- }
209
-
210
- async function walkFiles(dir: string): Promise<string[]> {
211
- if (!existsSync(dir)) return [];
212
- const entries = await readdir(dir, { withFileTypes: true });
213
- const files = await Promise.all(entries.map(async (entry) => {
214
- const path = join(dir, entry.name);
215
- if (entry.isDirectory()) return walkFiles(path);
216
- if (entry.isFile()) return [path];
217
- return [];
218
- }));
219
- return files.flat().sort();
220
- }
221
-
222
- function parseObject(raw: string, path: string): Record<string, unknown> {
223
- const parsed = parseYaml(raw);
224
- if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
225
- throw new Error(`${path} must parse as an object`);
226
- }
227
- return parsed as Record<string, unknown>;
228
- }
229
-
230
- function schemaRef(value: unknown, fallback: string): string {
231
- if (typeof value !== 'string' || value.length === 0) return fallback;
232
- assertNoRestrictedContent(value, 'schema_ref');
233
- return value;
234
- }
235
-
236
- function asString(value: unknown): string | undefined {
237
- return typeof value === 'string' && value.length > 0 ? value : undefined;
238
- }
239
-
240
- function policyFor(targetScope: Candidate['targetScope']): string {
241
- if (targetScope === 'workspace_local') return 'projection-policy:local-to-workspace';
242
- if (targetScope === 'subscriber_targeted') return 'projection-policy:workspace-to-subscriber';
243
- return 'projection-policy:workspace-to-org';
244
- }
245
-
246
- function buildEnvelope(options: {
247
- candidate: Candidate;
248
- createdAt: string;
249
- orgId?: string;
250
- workspaceId: string;
251
- defaultSeatId?: string;
252
- }): SyncEnvelope {
253
- const artifactRef = assertRepoRelative(options.candidate.artifactRef, 'artifact_ref');
254
- const schema = schemaRef(options.candidate.schemaRef, `${options.candidate.kind}.v0`);
255
- const summary = assertSummary(options.candidate.summary, 'summary');
256
- const evidenceRef = assertRepoRelative(artifactRef, 'evidence_ref');
257
- const seatId = options.candidate.seatId ?? options.defaultSeatId;
258
- if (options.orgId) assertSharedMemoryScopeId(options.orgId, 'source.org_id');
259
- assertSharedMemoryScopeId(options.workspaceId, 'source.workspace_id');
260
- if (seatId) assertSharedMemoryScopeId(seatId, 'source.seat_id');
261
- if (options.candidate.rawForPolicy) {
262
- assertNoRestrictedContent(options.candidate.rawForPolicy, artifactRef);
263
- }
264
- const basis = {
265
- kind: options.candidate.kind,
266
- schema,
267
- artifactRef,
268
- summary,
269
- targetScope: options.candidate.targetScope,
270
- policy: options.candidate.projectionPolicyRef,
271
- };
272
- return {
273
- schema: 'sync-envelope.v0',
274
- envelope_id: `sync:${stableHash(basis)}`,
275
- created_at: options.createdAt,
276
- source: {
277
- scope: options.candidate.sourceScope,
278
- ...(options.orgId ? { org_id: options.orgId } : {}),
279
- workspace_id: options.workspaceId,
280
- ...(seatId ? { seat_id: seatId } : {}),
281
- },
282
- target: {
283
- scope: options.candidate.targetScope,
284
- ...(options.orgId ? { org_id: options.orgId } : {}),
285
- workspace_id: options.workspaceId,
286
- subscriber_refs: [],
287
- },
288
- artifact: {
289
- kind: options.candidate.kind,
290
- schema_ref: schema,
291
- artifact_ref: artifactRef,
292
- summary,
293
- evidence_refs: [{ type: options.candidate.evidenceType, ref: evidenceRef }],
294
- },
295
- projection_policy_ref: options.candidate.projectionPolicyRef,
296
- visibility: options.candidate.targetScope,
297
- content_boundary: {
298
- metadata_only: true,
299
- raw_ledger_body_included: false,
300
- raw_event_body_included: false,
301
- raw_prompt_included: false,
302
- raw_tool_output_included: false,
303
- source_snippets_included: false,
304
- local_paths_included: false,
305
- },
306
- authority_boundary: {
307
- can_authorize_work: false,
308
- can_execute_work: false,
309
- can_attach_grant: false,
310
- can_replace_local_ledger: false,
311
- remote_is_execution_truth: false,
312
- },
313
- };
314
- }
315
-
316
- async function collectCandidates(projectRoot: string): Promise<{ candidates: Candidate[]; defaultSeatId?: string }> {
317
- const candidates: Candidate[] = [];
318
- let defaultSeatId: string | undefined;
319
-
320
- const readinessPath = defaultSeatReadinessPath(projectRoot);
321
- const readinessRaw = await readTextIfExists(readinessPath);
322
- if (readinessRaw) {
323
- const record = JSON.parse(readinessRaw) as SeatReadinessRecord;
324
- defaultSeatId = record.seat;
325
- candidates.push({
326
- kind: 'readiness_snapshot',
327
- sourceScope: 'seat_local',
328
- targetScope: 'org_projected',
329
- projectionPolicyRef: policyFor('org_projected'),
330
- schemaRef: record.schema_version,
331
- artifactRef: projectRelative(projectRoot, readinessPath),
332
- summary: `seat=${record.seat} status=${record.operational_status} model_tier=${record.model_tier}`,
333
- evidenceType: 'event',
334
- rawForPolicy: readinessRaw,
335
- seatId: record.seat,
336
- });
337
- }
338
-
339
- const readinessEventsPath = defaultSeatReadinessEventsPath(projectRoot);
340
- const readinessEventsRaw = await readTextIfExists(readinessEventsPath);
341
- if (readinessEventsRaw?.trim()) {
342
- const count = readinessEventsRaw.trim().split('\n').length;
343
- candidates.push({
344
- kind: 'event_summary',
345
- sourceScope: 'seat_local',
346
- targetScope: 'org_projected',
347
- projectionPolicyRef: policyFor('org_projected'),
348
- schemaRef: 'seat-readiness-event.v0.1',
349
- artifactRef: projectRelative(projectRoot, readinessEventsPath),
350
- summary: `seat readiness event summary count=${count}`,
351
- evidenceType: 'event',
352
- rawForPolicy: readinessEventsRaw,
353
- seatId: defaultSeatId,
354
- });
355
- }
356
-
357
- const ledgerPath = join(projectRoot, 'docs', 'work', 'ledger', 'LEDGER.md');
358
- const ledgerRaw = await readTextIfExists(ledgerPath);
359
- if (ledgerRaw?.trim()) {
360
- candidates.push({
361
- kind: 'ledger_entry',
362
- sourceScope: 'seat_local',
363
- targetScope: 'workspace_local',
364
- projectionPolicyRef: policyFor('workspace_local'),
365
- schemaRef: 'local-ledger.v0',
366
- artifactRef: projectRelative(projectRoot, ledgerPath),
367
- summary: 'local ledger available for workspace aggregation',
368
- evidenceType: 'ledger',
369
- rawForPolicy: ledgerRaw,
370
- seatId: defaultSeatId,
371
- });
372
- }
373
-
374
- for (const receiptPath of await walkFiles(join(projectRoot, 'docs', 'work', 'receipts'))) {
375
- if (!/\.(ya?ml|json)$/i.test(receiptPath)) continue;
376
- const raw = await readFile(receiptPath, 'utf-8');
377
- const parsed = parseObject(raw, receiptPath);
378
- const ref = projectRelative(projectRoot, receiptPath);
379
- const schema = schemaRef(parsed.schema, 'receipt.v0');
380
- const taskRef = asString(parsed.task_ref) ?? asString(parsed.receipt_id) ?? asString(parsed.record_id) ?? 'unknown';
381
- candidates.push({
382
- kind: 'receipt_metadata',
383
- sourceScope: 'workspace_local',
384
- targetScope: 'org_projected',
385
- projectionPolicyRef: policyFor('org_projected'),
386
- schemaRef: schema,
387
- artifactRef: ref,
388
- summary: `receipt metadata schema=${schema} task=${taskRef}`,
389
- evidenceType: 'receipt',
390
- rawForPolicy: raw,
391
- seatId: asString(parsed.seat) ?? defaultSeatId,
392
- });
393
- if (parsed.schema === 'operational-dispatch-record.v0') {
394
- candidates.push({
395
- kind: 'dispatch_record',
396
- sourceScope: 'workspace_local',
397
- targetScope: 'org_projected',
398
- projectionPolicyRef: policyFor('org_projected'),
399
- schemaRef: 'operational-dispatch-record.v0',
400
- artifactRef: ref,
401
- summary: `dispatch record decision=${asString(parsed.decision) ?? 'unknown'} task=${taskRef}`,
402
- evidenceType: 'receipt',
403
- rawForPolicy: raw,
404
- seatId: asString(parsed.seat) ?? defaultSeatId,
405
- });
406
- }
407
- }
408
-
409
- for (const capsulePath of await walkFiles(join(projectRoot, '.etiquette', 'exports'))) {
410
- if (!/\.json$/i.test(capsulePath)) continue;
411
- const raw = await readFile(capsulePath, 'utf-8');
412
- const parsed = JSON.parse(raw) as Record<string, unknown>;
413
- if (parsed.schema !== 'recall-capsule.v0') continue;
414
- const title = asString(parsed.title) ?? 'recall capsule';
415
- candidates.push({
416
- kind: 'memory_capsule',
417
- sourceScope: 'workspace_local',
418
- targetScope: 'org_projected',
419
- projectionPolicyRef: policyFor('org_projected'),
420
- schemaRef: 'recall-capsule.v0',
421
- artifactRef: projectRelative(projectRoot, capsulePath),
422
- summary: `memory capsule title=${title}`,
423
- evidenceType: 'memory',
424
- rawForPolicy: raw,
425
- seatId: defaultSeatId,
426
- });
427
- }
428
-
429
- return { candidates, defaultSeatId };
430
- }
431
-
432
- export async function stageSyncOutbox(options: StageSyncOutboxOptions): Promise<SyncStageResult> {
433
- const projectRoot = resolve(options.projectRoot);
434
- const createdAt = isoNow(options.at);
435
- const workspaceId = options.workspaceId ?? DEFAULT_WORKSPACE_ID;
436
- assertSharedMemoryScopeId(workspaceId, '--workspace-id');
437
- if (options.orgId) assertSharedMemoryScopeId(options.orgId, '--org-id');
438
- if (options.seatId) assertSharedMemoryScopeId(options.seatId, '--seat-id');
439
- const outboxDir = resolveOutboxDir(projectRoot, options.outboxDir);
440
- const { candidates, defaultSeatId } = await collectCandidates(projectRoot);
441
-
442
- await mkdir(outboxDir, { recursive: true });
443
- const result: SyncStageResult = {
444
- schema: 'sync-stage-result.v0',
445
- project_ref: '.',
446
- outbox_ref: outboxRelative(projectRoot, outboxDir),
447
- staged: 0,
448
- rejected: [],
449
- local_only: [],
450
- envelopes: [],
451
- authority_boundary: {
452
- can_authorize_work: false,
453
- can_execute_work: false,
454
- can_attach_grant: false,
455
- can_replace_local_ledger: false,
456
- hosted_plane_is_source_of_truth: false,
457
- },
458
- };
459
-
460
- for (const candidate of candidates) {
461
- try {
462
- const envelope = buildEnvelope({
463
- candidate,
464
- createdAt,
465
- orgId: options.orgId,
466
- workspaceId,
467
- defaultSeatId: options.seatId ?? defaultSeatId,
468
- });
469
- const fileName = `${envelope.envelope_id.replace(/[^A-Za-z0-9_-]+/g, '-')}.json`;
470
- const outPath = join(outboxDir, fileName);
471
- await mkdir(dirname(outPath), { recursive: true });
472
- await writeFile(outPath, `${JSON.stringify(envelope, null, 2)}\n`);
473
- result.staged += 1;
474
- result.envelopes.push({
475
- envelope_id: envelope.envelope_id,
476
- kind: envelope.artifact.kind,
477
- artifact_ref: envelope.artifact.artifact_ref,
478
- outbox_ref: outboxRelative(projectRoot, outPath),
479
- target_scope: envelope.target.scope,
480
- projection_policy_ref: envelope.projection_policy_ref,
481
- });
482
- } catch (err) {
483
- result.rejected.push({
484
- artifact_ref: candidate.artifactRef,
485
- kind: candidate.kind,
486
- reason: (err as Error).message,
487
- });
488
- }
489
- }
490
-
491
- return result;
492
- }
@@ -1,140 +0,0 @@
1
- import type {
2
- WorkflowCheckEnvelope,
3
- WorkflowCheckResult,
4
- WorkflowMode,
5
- WorkflowModeType,
6
- WorkflowRecommendation,
7
- } from './types';
8
-
9
- function modeRefMatches(mode: WorkflowMode, ref: string): boolean {
10
- return ref === mode.mode_id || ref === `workflow-mode:${mode.mode_id}`;
11
- }
12
-
13
- function check(status: WorkflowRecommendation, check_id: string, summary: string): WorkflowCheckResult['checks'][number] {
14
- return { check_id, status, summary };
15
- }
16
-
17
- function recommendationFor(missing: string[], warnings: string[]): WorkflowRecommendation {
18
- if (missing.length > 0) return 'block';
19
- if (warnings.length > 0) return 'warn';
20
- return 'pass';
21
- }
22
-
23
- export function explainWorkflowMode(modeType: WorkflowModeType): {
24
- mode_type: WorkflowModeType;
25
- summary: string;
26
- use_when: string[];
27
- boundary: string[];
28
- } {
29
- if (modeType === 'deliberative') {
30
- return {
31
- mode_type: modeType,
32
- summary: 'Slow, human-reviewed, grant-heavy workflow posture.',
33
- use_when: ['doctrine', 'architecture', 'security posture', 'subjective tradeoffs'],
34
- boundary: ['consensus recommends only', 'explicit grant required before mutation', 'dissent must be preserved'],
35
- };
36
- }
37
- if (modeType === 'expedient') {
38
- return {
39
- mode_type: modeType,
40
- summary: 'Fast, bounded, rubric-heavy, evidence-first workflow posture.',
41
- use_when: ['health checks', 'fixture runs', 'known-safe mechanical work', 'local status probes'],
42
- boundary: ['not a shortcut', 'not lower safety', 'requires bounded scope, validation, and receipt proof'],
43
- };
44
- }
45
- return {
46
- mode_type: modeType,
47
- summary: 'Slow upstream drafting, explicit grant hinge, fast local execution, receipt proof.',
48
- use_when: ['current v3/Etiquette implementation lanes', 'reviewed doctrine feeding bounded local execution'],
49
- boundary: ['grant_ref is the phase boundary', 'execution cannot begin before the grant hinge when mutating state'],
50
- };
51
- }
52
-
53
- export function evaluateWorkflowCheck(mode: WorkflowMode, envelope: WorkflowCheckEnvelope): WorkflowCheckResult {
54
- const missing: string[] = [];
55
- const warnings: string[] = [];
56
- const checks: WorkflowCheckResult['checks'] = [];
57
-
58
- if (!modeRefMatches(mode, envelope.workflow_mode_ref)) {
59
- warnings.push('workflow_mode_ref_mismatch');
60
- checks.push(check('warn', 'workflow_mode_ref_matches', 'Envelope workflow_mode_ref does not match the loaded mode id.'));
61
- } else {
62
- checks.push(check('pass', 'workflow_mode_ref_matches', 'Envelope references the loaded workflow mode.'));
63
- }
64
-
65
- if (envelope.requested_mode_type !== mode.mode_type) {
66
- missing.push('requested_mode_type');
67
- checks.push(check('block', 'requested_mode_type_matches', 'Envelope requested mode type does not match loaded mode.'));
68
- } else {
69
- checks.push(check('pass', 'requested_mode_type_matches', 'Envelope requested mode type matches loaded mode.'));
70
- }
71
-
72
- if (envelope.advisory_consensus_claims_authority) {
73
- missing.push('advisory_consensus_claims_authority_false');
74
- checks.push(check('block', 'advisory_consensus_not_authority', 'Advisory consensus cannot authorize work.'));
75
- } else {
76
- checks.push(check('pass', 'advisory_consensus_not_authority', 'No advisory consensus authority claim present.'));
77
- }
78
-
79
- if (envelope.mutates_state && mode.grant.required && !envelope.grant_ref) {
80
- missing.push('grant_ref');
81
- checks.push(check('block', 'grant_hinge_satisfied', 'Mutating execution under this mode requires grant_ref.'));
82
- } else {
83
- checks.push(check('pass', 'grant_hinge_satisfied', 'Grant hinge is satisfied or not required for this envelope.'));
84
- }
85
-
86
- if (envelope.mutates_state && mode.proof.receipt_required && !envelope.receipt_ref) {
87
- missing.push('receipt_ref');
88
- checks.push(check('block', 'receipt_proof_declared', 'Mutating execution requires a receipt_ref or receipt plan.'));
89
- } else {
90
- checks.push(check('pass', 'receipt_proof_declared', 'Receipt proof is declared or not required.'));
91
- }
92
-
93
- if (mode.proof.validation_required && envelope.validation_refs.length === 0) {
94
- missing.push('validation_refs');
95
- checks.push(check('block', 'validation_evidence_present', 'Mode requires validation evidence refs.'));
96
- } else {
97
- checks.push(check('pass', 'validation_evidence_present', 'Validation evidence is present or not required.'));
98
- }
99
-
100
- if (mode.mode_type === 'expedient') {
101
- if (!envelope.bounded) missing.push('bounded');
102
- if (!envelope.preauthorized) missing.push('preauthorized');
103
- if (envelope.risk === 'high') missing.push('risk_not_high');
104
- checks.push(check(
105
- envelope.bounded && envelope.preauthorized && envelope.risk !== 'high' ? 'pass' : 'block',
106
- 'expedient_claim_is_valid',
107
- 'Expedient mode requires bounded scope, preauthorization, non-high risk, validation, and receipt proof.',
108
- ));
109
- }
110
-
111
- if (mode.mode_type === 'deliberative' && mode.drafting.requires_review && !envelope.consensus_ref) {
112
- warnings.push('deliberative_without_consensus_ref');
113
- checks.push(check('warn', 'deliberative_review_visible', 'Deliberative mode should cite consensus or review state.'));
114
- }
115
-
116
- if (mode.mode_type === 'mixed' && mode.execution.dispatch === 'immediate_after_grant' && envelope.mutates_state && !envelope.grant_ref) {
117
- if (!missing.includes('grant_ref')) missing.push('grant_ref');
118
- checks.push(check('block', 'mixed_grant_before_execution', 'Mixed mode cannot execute mutating work before grant_ref.'));
119
- } else if (mode.mode_type === 'mixed') {
120
- checks.push(check('pass', 'mixed_grant_before_execution', 'Mixed grant hinge is satisfied or work is non-mutating.'));
121
- }
122
-
123
- return {
124
- schema: 'workflow-check-receipt.v0',
125
- task_ref: envelope.task_ref,
126
- mode_id: mode.mode_id,
127
- mode_type: mode.mode_type,
128
- recommendation: recommendationFor([...new Set(missing)], [...new Set(warnings)]),
129
- missing: [...new Set(missing)],
130
- warnings: [...new Set(warnings)],
131
- checks,
132
- authority_boundary: {
133
- can_authorize: false,
134
- can_attach_grant: false,
135
- consensus_can_authorize: false,
136
- mode_is_engine: false,
137
- requires_grant: mode.grant.required,
138
- },
139
- };
140
- }