@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,326 +0,0 @@
1
- import { readFile } from 'node:fs/promises';
2
- import { extname } from 'node:path';
3
- import { parse as parseYaml } from 'yaml';
4
- import {
5
- CHECK_EVALUATIONS,
6
- CHECK_SEVERITIES,
7
- CONTENT_CLASSES,
8
- FIXTURE_KINDS,
9
- PROMOTION_EVIDENCE,
10
- RUBRIC_OUTCOMES,
11
- RUBRIC_STATUSES,
12
- RUN_ENVIRONMENTS,
13
- type CheckEvaluation,
14
- type CheckSeverity,
15
- type ConformanceRubric,
16
- type ContentClass,
17
- type FixtureKind,
18
- type PromotionEvidence,
19
- type PromotionPolicy,
20
- type Rubric,
21
- type RubricFixture,
22
- type RubricFixtureSet,
23
- type RubricOutcome,
24
- type RubricStatus,
25
- type RunEnvironment,
26
- type SandboxRunReceipt,
27
- type ValidationCheck,
28
- type ValidationRubric,
29
- } from './types';
30
- import { assertSharedMemoryScopeId, assertSharedMemoryText, detectMemoryRiskFlags } from '../shared/sanitize';
31
-
32
- type JsonRecord = Record<string, unknown>;
33
-
34
- function isRecord(value: unknown): value is JsonRecord {
35
- return typeof value === 'object' && value !== null && !Array.isArray(value);
36
- }
37
-
38
- async function readStructured(path: string): Promise<unknown> {
39
- const raw = await readFile(path, 'utf-8');
40
- if (extname(path).toLowerCase() === '.json') {
41
- return JSON.parse(raw) as unknown;
42
- }
43
- return parseYaml(raw) as unknown;
44
- }
45
-
46
- function asRecord(value: unknown, name: string): JsonRecord {
47
- if (!isRecord(value)) throw new Error(`${name} must be an object`);
48
- return value;
49
- }
50
-
51
- function asString(record: JsonRecord, key: string): string {
52
- const value = record[key];
53
- if (typeof value !== 'string' || value.trim().length === 0) {
54
- throw new Error(`${key} must be a non-empty string`);
55
- }
56
- return value;
57
- }
58
-
59
- function optionalStringArray(record: JsonRecord, key: string): string[] {
60
- const value = record[key];
61
- if (value === undefined) return [];
62
- if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
63
- throw new Error(`${key} must be a string array`);
64
- }
65
- return value as string[];
66
- }
67
-
68
- function asStringArray(record: JsonRecord, key: string): string[] {
69
- const value = record[key];
70
- if (!Array.isArray(value) || value.some((item) => typeof item !== 'string')) {
71
- throw new Error(`${key} must be a string array`);
72
- }
73
- return value as string[];
74
- }
75
-
76
- function asEnum<T extends readonly string[]>(values: T, raw: unknown, key: string): T[number] {
77
- if (typeof raw !== 'string' || !(values as readonly string[]).includes(raw)) {
78
- throw new Error(`${key} must be one of: ${values.join(', ')}`);
79
- }
80
- return raw as T[number];
81
- }
82
-
83
- function asOptionalEnum<T extends readonly string[]>(values: T, raw: unknown, key: string, fallback: T[number]): T[number] {
84
- if (raw === undefined) return fallback;
85
- return asEnum(values, raw, key);
86
- }
87
-
88
- function asContentClasses(record: JsonRecord, key: string): ContentClass[] {
89
- return asStringArray(record, key).map((item) => asEnum(CONTENT_CLASSES, item, `${key}[]`) as ContentClass);
90
- }
91
-
92
- function assertSafeId(value: string, key: string): void {
93
- assertSharedMemoryScopeId(value, key);
94
- }
95
-
96
- function assertSafeText(value: string, key: string): void {
97
- assertSharedMemoryText(value, key);
98
- }
99
-
100
- function assertSafeRubricProse(value: string, key: string): void {
101
- const flags = detectMemoryRiskFlags(value).filter((flag) => (
102
- flag !== 'contains_raw_prompt' && flag !== 'contains_raw_tool_output'
103
- ));
104
- if (flags.length > 0) {
105
- throw new Error(`${key} contains restricted shared-memory content: ${flags.join(', ')}`);
106
- }
107
- }
108
-
109
- function assertSemverish(value: string, key: string): void {
110
- if (!/^\d+\.\d+\.\d+(?:[-+][A-Za-z0-9_.-]+)?$/.test(value)) {
111
- throw new Error(`${key} must be semver-like (x.y.z)`);
112
- }
113
- }
114
-
115
- function normalizeFixtureRefs(record: JsonRecord, key: string): string[] {
116
- const refs = optionalStringArray(record, key);
117
- for (const ref of refs) assertSafeText(ref, `${key}[]`);
118
- return refs;
119
- }
120
-
121
- function parseValidationChecks(value: unknown): ValidationCheck[] {
122
- if (!Array.isArray(value) || value.length === 0) throw new Error('checks must be a non-empty array');
123
- return value.map((item, index) => {
124
- const record = asRecord(item, `checks[${index}]`);
125
- const check: ValidationCheck = {
126
- check_id: asString(record, 'check_id'),
127
- description: asString(record, 'description'),
128
- severity: asEnum(CHECK_SEVERITIES, record.severity, 'severity') as CheckSeverity,
129
- evaluation: asEnum(CHECK_EVALUATIONS, record.evaluation, 'evaluation') as CheckEvaluation,
130
- evidence_required: asStringArray(record, 'evidence_required'),
131
- };
132
- assertSafeId(check.check_id, 'check_id');
133
- assertSafeRubricProse(check.description, 'description');
134
- for (const evidence of check.evidence_required) assertSafeId(evidence, 'evidence_required[]');
135
- return check;
136
- });
137
- }
138
-
139
- function validateCommonRubric(record: JsonRecord): {
140
- rubric_id: string;
141
- rubric_version: string;
142
- known_bad_fixtures: string[];
143
- known_good_fixtures: string[];
144
- owner_principal_ref: string;
145
- status: RubricStatus;
146
- } {
147
- const rubricId = asString(record, 'rubric_id');
148
- const version = asString(record, 'rubric_version');
149
- assertSafeId(rubricId, 'rubric_id');
150
- assertSemverish(version, 'rubric_version');
151
- const owner = asString(record, 'owner_principal_ref');
152
- assertSafeText(owner, 'owner_principal_ref');
153
- return {
154
- rubric_id: rubricId,
155
- rubric_version: version,
156
- known_bad_fixtures: normalizeFixtureRefs(record, 'known_bad_fixtures'),
157
- known_good_fixtures: normalizeFixtureRefs(record, 'known_good_fixtures'),
158
- owner_principal_ref: owner,
159
- status: asEnum(RUBRIC_STATUSES, record.status, 'status') as RubricStatus,
160
- };
161
- }
162
-
163
- export function parseRubric(value: unknown): Rubric {
164
- const record = asRecord(value, 'rubric');
165
- const common = validateCommonRubric(record);
166
- if (typeof record.protocol_surface === 'string') {
167
- const failure = asRecord(record.failure_semantics, 'failure_semantics');
168
- const rubric: ConformanceRubric = {
169
- kind: 'conformance',
170
- ...common,
171
- protocol_surface: asString(record, 'protocol_surface'),
172
- schema_refs: asStringArray(record, 'schema_refs'),
173
- forbidden_content_classes: asContentClasses(record, 'forbidden_content_classes'),
174
- citation_rules: optionalStringArray(record, 'citation_rules'),
175
- sanitizer_expectations: optionalStringArray(record, 'sanitizer_expectations'),
176
- failure_semantics: {
177
- on_reject: asEnum(['block', 'warn'] as const, failure.on_reject, 'failure_semantics.on_reject'),
178
- on_uncertain: asEnum(['warn', 'require_review'] as const, failure.on_uncertain, 'failure_semantics.on_uncertain'),
179
- },
180
- };
181
- assertSafeId(rubric.protocol_surface, 'protocol_surface');
182
- if (rubric.schema_refs.length === 0) throw new Error('schema_refs must not be empty');
183
- for (const ref of rubric.schema_refs) assertSafeText(ref, 'schema_refs[]');
184
- for (const rule of rubric.citation_rules) assertSafeRubricProse(rule, 'citation_rules[]');
185
- for (const expectation of rubric.sanitizer_expectations) assertSafeRubricProse(expectation, 'sanitizer_expectations[]');
186
- return rubric;
187
- }
188
-
189
- const rubric: ValidationRubric = {
190
- kind: 'validation',
191
- ...common,
192
- domain: asString(record, 'domain'),
193
- artifact_type: asString(record, 'artifact_type'),
194
- title: asString(record, 'title'),
195
- summary: asString(record, 'summary'),
196
- checks: parseValidationChecks(record.checks),
197
- evidence_requirements: asStringArray(record, 'evidence_requirements'),
198
- };
199
- assertSafeId(rubric.domain, 'domain');
200
- assertSafeId(rubric.artifact_type, 'artifact_type');
201
- assertSafeRubricProse(rubric.title, 'title');
202
- assertSafeRubricProse(rubric.summary, 'summary');
203
- for (const evidence of rubric.evidence_requirements) assertSafeId(evidence, 'evidence_requirements[]');
204
- return rubric;
205
- }
206
-
207
- export async function loadRubric(path: string): Promise<Rubric> {
208
- return parseRubric(await readStructured(path));
209
- }
210
-
211
- function fixturePayload(record: JsonRecord): unknown {
212
- if (record.input !== undefined) return record.input;
213
- if (record.input_text !== undefined) return record.input_text;
214
- if (record.artifact !== undefined) return record.artifact;
215
- if (record.payload !== undefined) return record.payload;
216
- if (record.metadata !== undefined) return record.metadata;
217
- if (record.sample !== undefined) return record.sample;
218
- return undefined;
219
- }
220
-
221
- function parseFixture(value: unknown, index: number): RubricFixture {
222
- const record = asRecord(value, `fixtures[${index}]`);
223
- const fixture: RubricFixture = {
224
- fixture_id: asString(record, 'fixture_id'),
225
- fixture_version: asString(record, 'fixture_version'),
226
- content_class: asEnum(CONTENT_CLASSES, record.content_class, 'content_class') as ContentClass,
227
- fixture_kind: asEnum(FIXTURE_KINDS, record.fixture_kind, 'fixture_kind') as FixtureKind,
228
- artifact_type: asString(record, 'artifact_type'),
229
- input_ref: asString(record, 'input_ref'),
230
- expected: asEnum(RUBRIC_OUTCOMES, record.expected, 'expected') as RubricOutcome,
231
- rationale: asString(record, 'rationale'),
232
- input: fixturePayload(record),
233
- };
234
- assertSafeId(fixture.fixture_id, 'fixture_id');
235
- assertSemverish(fixture.fixture_version, 'fixture_version');
236
- assertSafeId(fixture.artifact_type, 'artifact_type');
237
- assertSafeText(fixture.input_ref, 'input_ref');
238
- assertSafeRubricProse(fixture.rationale, 'rationale');
239
- return fixture;
240
- }
241
-
242
- export function parseFixtureSet(value: unknown): RubricFixtureSet {
243
- const record = asRecord(value, 'fixture_set');
244
- const fixturesRaw = record.fixtures;
245
- if (!Array.isArray(fixturesRaw) || fixturesRaw.length === 0) {
246
- throw new Error('fixtures must be a non-empty array');
247
- }
248
- const fixtureSet: RubricFixtureSet = {
249
- fixture_set_id: asString(record, 'fixture_set_id'),
250
- fixture_set_version: typeof record.fixture_set_version === 'string' ? record.fixture_set_version : '0.1.0',
251
- fixtures: fixturesRaw.map(parseFixture),
252
- };
253
- assertSafeText(fixtureSet.fixture_set_id, 'fixture_set_id');
254
- assertSemverish(fixtureSet.fixture_set_version, 'fixture_set_version');
255
- return fixtureSet;
256
- }
257
-
258
- export async function loadFixtureSet(path: string): Promise<RubricFixtureSet> {
259
- return parseFixtureSet(await readStructured(path));
260
- }
261
-
262
- export function validateSandboxReceipt(receipt: SandboxRunReceipt): void {
263
- if (receipt.schema !== 'sandbox-run-receipt.v0') throw new Error('receipt schema must be sandbox-run-receipt.v0');
264
- assertSafeText(receipt.receipt_id, 'receipt_id');
265
- assertSafeId(receipt.rubric_id, 'rubric_id');
266
- assertSemverish(receipt.rubric_version, 'rubric_version');
267
- assertSafeText(receipt.fixture_set_ref, 'fixture_set_ref');
268
- if (!Number.isFinite(Date.parse(receipt.run_at))) throw new Error('run_at must be an ISO-8601 timestamp');
269
- assertSafeText(receipt.run_by, 'run_by');
270
- asEnum(RUN_ENVIRONMENTS, receipt.run_environment, 'run_environment');
271
- if (!Array.isArray(receipt.results)) throw new Error('results must be an array');
272
- for (const [index, result] of receipt.results.entries()) {
273
- assertSafeId(result.fixture_id, `results[${index}].fixture_id`);
274
- asEnum(RUBRIC_OUTCOMES, result.expected, `results[${index}].expected`);
275
- asEnum(RUBRIC_OUTCOMES, result.actual, `results[${index}].actual`);
276
- assertSafeText(result.reason, `results[${index}].reason`);
277
- }
278
- const unsafe = detectMemoryRiskFlags(JSON.stringify(receipt));
279
- if (unsafe.length > 0) {
280
- throw new Error(`sandbox receipt contains restricted shared content: ${unsafe.join(', ')}`);
281
- }
282
- }
283
-
284
- export function parseSandboxReceipt(value: unknown): SandboxRunReceipt {
285
- const record = asRecord(value, 'sandbox_run_receipt') as unknown as SandboxRunReceipt;
286
- validateSandboxReceipt(record);
287
- return record;
288
- }
289
-
290
- export async function loadSandboxReceipt(path: string): Promise<SandboxRunReceipt> {
291
- return parseSandboxReceipt(await readStructured(path));
292
- }
293
-
294
- export function parsePromotionPolicy(value: unknown): PromotionPolicy {
295
- const record = asRecord(value, 'promotion_policy');
296
- const applies = asRecord(record.applies_to, 'applies_to');
297
- const policy: PromotionPolicy = {
298
- policy_id: asString(record, 'policy_id'),
299
- policy_version: asString(record, 'policy_version'),
300
- applies_to: {
301
- domains: optionalStringArray(applies, 'domains'),
302
- artifact_types: optionalStringArray(applies, 'artifact_types'),
303
- protocol_surfaces: optionalStringArray(applies, 'protocol_surfaces'),
304
- },
305
- proposer_actor: asString(record, 'proposer_actor'),
306
- reviewer_set: asStringArray(record, 'reviewer_set'),
307
- required_evidence: asStringArray(record, 'required_evidence')
308
- .map((item) => asEnum(PROMOTION_EVIDENCE, item, 'required_evidence[]') as PromotionEvidence),
309
- grant_ref: record.grant_ref === null || record.grant_ref === undefined ? null : asString(record, 'grant_ref'),
310
- activation_status: asEnum(
311
- ['proposed', 'ready_for_grant', 'active', 'rejected', 'retired'] as const,
312
- record.activation_status,
313
- 'activation_status',
314
- ),
315
- };
316
- assertSafeId(policy.policy_id, 'policy_id');
317
- assertSemverish(policy.policy_version, 'policy_version');
318
- assertSafeText(policy.proposer_actor, 'proposer_actor');
319
- for (const reviewer of policy.reviewer_set) assertSafeText(reviewer, 'reviewer_set[]');
320
- if (policy.grant_ref) assertSafeText(policy.grant_ref, 'grant_ref');
321
- return policy;
322
- }
323
-
324
- export async function loadPromotionPolicy(path: string): Promise<PromotionPolicy> {
325
- return parsePromotionPolicy(await readStructured(path));
326
- }
@@ -1,54 +0,0 @@
1
- import {
2
- PROMOTION_EVIDENCE,
3
- type PromotionEvidence,
4
- type PromotionPolicy,
5
- type PromotionReadiness,
6
- type Rubric,
7
- type SandboxRunReceipt,
8
- } from './types';
9
-
10
- export function evaluatePromotionReadiness(options: {
11
- rubric: Rubric;
12
- receipt: SandboxRunReceipt;
13
- policy?: PromotionPolicy;
14
- grantRef?: string;
15
- }): PromotionReadiness {
16
- const evidence: Record<PromotionEvidence, boolean> = {
17
- schema_valid_rubric: true,
18
- schema_valid_fixture_set: options.receipt.fixture_set_ref.startsWith('fixture-set:'),
19
- sandbox_run_receipt: options.receipt.summary.unexpected_outcomes === 0,
20
- known_bad_rejection: options.receipt.summary.known_bad_rejected > 0,
21
- known_good_acceptance: options.receipt.summary.known_good_accepted > 0,
22
- };
23
- if (options.receipt.rubric_id !== options.rubric.rubric_id) {
24
- evidence.sandbox_run_receipt = false;
25
- }
26
- if (options.receipt.rubric_version !== options.rubric.rubric_version) {
27
- evidence.sandbox_run_receipt = false;
28
- }
29
-
30
- const required = options.policy?.required_evidence ?? [...PROMOTION_EVIDENCE];
31
- const missing: string[] = required.filter((key) => !evidence[key]);
32
- const grantRef = options.grantRef ?? options.policy?.grant_ref ?? null;
33
- if (!grantRef) missing.push('grant_ref');
34
-
35
- const evidenceComplete = required.every((key) => evidence[key]);
36
- const recommendation = !evidenceComplete
37
- ? 'blocked'
38
- : grantRef
39
- ? 'ready_to_activate'
40
- : 'ready_for_grant';
41
-
42
- return {
43
- rubric_id: options.rubric.rubric_id,
44
- rubric_version: options.rubric.rubric_version,
45
- evidence,
46
- missing,
47
- grant_ref: grantRef,
48
- recommendation,
49
- authority_boundary: {
50
- can_activate_rubric: false,
51
- requires_operator_principal_grant: true,
52
- },
53
- };
54
- }
@@ -1,159 +0,0 @@
1
- import { createHash } from 'node:crypto';
2
- import { mkdir, writeFile } from 'node:fs/promises';
3
- import { dirname } from 'node:path';
4
- import { stringify as stringifyYaml } from 'yaml';
5
- import { detectMemoryRiskFlags } from '../shared/sanitize';
6
- import {
7
- CONTENT_CLASSES,
8
- RUN_ENVIRONMENTS,
9
- type ContentClass,
10
- type Rubric,
11
- type RubricFixture,
12
- type RubricFixtureSet,
13
- type RubricOutcome,
14
- type RunEnvironment,
15
- type SandboxRunReceipt,
16
- } from './types';
17
- import { validateSandboxReceipt } from './loader';
18
-
19
- function sha(input: string): string {
20
- return createHash('sha256').update(input).digest('hex').slice(0, 12);
21
- }
22
-
23
- function stringifyInput(value: unknown, fallback: string): string {
24
- if (value === undefined) return fallback;
25
- if (typeof value === 'string') return value;
26
- return JSON.stringify(value);
27
- }
28
-
29
- function flagsToContentClasses(value: string): ContentClass[] {
30
- const classes = new Set<ContentClass>();
31
- for (const flag of detectMemoryRiskFlags(value)) {
32
- if (flag === 'contains_local_path') classes.add('local_path');
33
- if (flag === 'contains_hostname') classes.add('hostname');
34
- if (flag === 'contains_credential') classes.add('credential');
35
- if (flag === 'contains_raw_prompt') classes.add('raw_prompt');
36
- if (flag === 'contains_raw_tool_output') classes.add('raw_tool_output');
37
- if (flag === 'contains_source_snippet') classes.add('source_snippet');
38
- }
39
- if (/\b(?:uncited|citation missing|no evidence_ref|without citation)\b/i.test(value)) {
40
- classes.add('uncited_claim');
41
- }
42
- if (/\b(?:unsupported_schema_field|unknown field|extra field|not in schema)\b/i.test(value)) {
43
- classes.add('unsupported_schema_field');
44
- }
45
- return [...classes];
46
- }
47
-
48
- function declaredContentClasses(rubric: Rubric, fixtures: RubricFixture[]): ContentClass[] {
49
- if (rubric.kind === 'conformance') return rubric.forbidden_content_classes;
50
- const fromKnownBad = fixtures
51
- .filter((fixture) => fixture.fixture_kind === 'known_bad')
52
- .map((fixture) => fixture.content_class);
53
- return [...new Set(fromKnownBad)];
54
- }
55
-
56
- function validateFixtureCoverage(classes: ContentClass[], fixtureSet: RubricFixtureSet): void {
57
- if (classes.length === 0) throw new Error('rubric sandbox needs at least one declared content class');
58
- const good = fixtureSet.fixtures.filter((fixture) => fixture.fixture_kind === 'known_good');
59
- if (good.length < 1) {
60
- throw new Error('fixture coverage requires at least one known_good fixture');
61
- }
62
- for (const contentClass of classes) {
63
- const badCount = fixtureSet.fixtures.filter(
64
- (fixture) => fixture.fixture_kind === 'known_bad' && fixture.content_class === contentClass,
65
- ).length;
66
- if (badCount < 2) {
67
- throw new Error(`fixture coverage requires at least 2 known_bad fixtures for ${contentClass}`);
68
- }
69
- }
70
- }
71
-
72
- function validationSignal(value: unknown): RubricOutcome | undefined {
73
- if (typeof value === 'string') {
74
- const match = value.match(/\bvalidation_signal:(pass|warn|reject)\b/i);
75
- return match ? match[1].toLowerCase() as RubricOutcome : undefined;
76
- }
77
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
78
- const signal = (value as Record<string, unknown>).validation_signal;
79
- if (signal === 'pass' || signal === 'warn' || signal === 'reject') return signal;
80
- }
81
- return undefined;
82
- }
83
-
84
- function evaluateFixture(rubric: Rubric, fixture: RubricFixture): { actual: RubricOutcome; reason: string } {
85
- const content = stringifyInput(fixture.input, fixture.input_ref);
86
- if (rubric.kind === 'validation') {
87
- const signal = validationSignal(fixture.input);
88
- if (signal) return { actual: signal, reason: `validation fixture declared ${signal}` };
89
- }
90
-
91
- const detected = flagsToContentClasses(content);
92
- const forbidden = rubric.kind === 'conformance'
93
- ? rubric.forbidden_content_classes
94
- : CONTENT_CLASSES.filter((contentClass) => contentClass === fixture.content_class);
95
- const matched = detected.filter((contentClass) => forbidden.includes(contentClass));
96
- if (matched.length > 0) {
97
- return {
98
- actual: 'reject',
99
- reason: `detected forbidden content class: ${matched.sort().join(', ')}`,
100
- };
101
- }
102
- return {
103
- actual: 'pass',
104
- reason: 'no forbidden content classes detected',
105
- };
106
- }
107
-
108
- export async function runRubricSandbox(options: {
109
- rubric: Rubric;
110
- fixtureSet: RubricFixtureSet;
111
- runBy: string;
112
- runEnvironment?: RunEnvironment;
113
- runAt?: string;
114
- receiptPath?: string;
115
- }): Promise<SandboxRunReceipt> {
116
- const runEnvironment = options.runEnvironment ?? 'local';
117
- if (!RUN_ENVIRONMENTS.includes(runEnvironment)) {
118
- throw new Error(`run_environment must be one of: ${RUN_ENVIRONMENTS.join(', ')}`);
119
- }
120
- const classes = declaredContentClasses(options.rubric, options.fixtureSet.fixtures);
121
- validateFixtureCoverage(classes, options.fixtureSet);
122
- const runAt = options.runAt ? new Date(options.runAt).toISOString() : new Date().toISOString();
123
- const results = options.fixtureSet.fixtures.map((fixture) => {
124
- const evaluated = evaluateFixture(options.rubric, fixture);
125
- return {
126
- fixture_id: fixture.fixture_id,
127
- expected: fixture.expected,
128
- actual: evaluated.actual,
129
- reason: evaluated.reason,
130
- };
131
- });
132
- const receipt: SandboxRunReceipt = {
133
- schema: 'sandbox-run-receipt.v0',
134
- receipt_id: `sandbox-run:${options.rubric.rubric_id}:${sha(`${options.fixtureSet.fixture_set_id}:${runAt}`)}`,
135
- rubric_id: options.rubric.rubric_id,
136
- rubric_version: options.rubric.rubric_version,
137
- fixture_set_ref: `fixture-set:${options.fixtureSet.fixture_set_id}`,
138
- results,
139
- summary: {
140
- total_fixtures: results.length,
141
- known_bad_rejected: options.fixtureSet.fixtures
142
- .filter((fixture, index) => fixture.fixture_kind === 'known_bad' && results[index]?.actual === 'reject')
143
- .length,
144
- known_good_accepted: options.fixtureSet.fixtures
145
- .filter((fixture, index) => fixture.fixture_kind === 'known_good' && results[index]?.actual === 'pass')
146
- .length,
147
- unexpected_outcomes: results.filter((result) => result.actual !== result.expected).length,
148
- },
149
- run_at: runAt,
150
- run_by: options.runBy,
151
- run_environment: runEnvironment,
152
- };
153
- validateSandboxReceipt(receipt);
154
- if (options.receiptPath) {
155
- await mkdir(dirname(options.receiptPath), { recursive: true });
156
- await writeFile(options.receiptPath, stringifyYaml(receipt));
157
- }
158
- return receipt;
159
- }
@@ -1,158 +0,0 @@
1
- export const RUBRIC_STATUSES = ['rubric_candidate', 'active_rubric', 'retired'] as const;
2
- export type RubricStatus = typeof RUBRIC_STATUSES[number];
3
-
4
- export const CHECK_SEVERITIES = ['block', 'warn', 'info'] as const;
5
- export type CheckSeverity = typeof CHECK_SEVERITIES[number];
6
-
7
- export const CHECK_EVALUATIONS = ['deterministic', 'reviewer_judgment', 'external_tool'] as const;
8
- export type CheckEvaluation = typeof CHECK_EVALUATIONS[number];
9
-
10
- export const CONTENT_CLASSES = [
11
- 'local_path',
12
- 'hostname',
13
- 'credential',
14
- 'raw_prompt',
15
- 'raw_tool_output',
16
- 'source_snippet',
17
- 'uncited_claim',
18
- 'unsupported_schema_field',
19
- ] as const;
20
- export type ContentClass = typeof CONTENT_CLASSES[number];
21
-
22
- export const FIXTURE_KINDS = ['known_bad', 'known_good'] as const;
23
- export type FixtureKind = typeof FIXTURE_KINDS[number];
24
-
25
- export const RUBRIC_OUTCOMES = ['pass', 'warn', 'reject'] as const;
26
- export type RubricOutcome = typeof RUBRIC_OUTCOMES[number];
27
-
28
- export const RUN_ENVIRONMENTS = ['local', 'sandbox-isolated'] as const;
29
- export type RunEnvironment = typeof RUN_ENVIRONMENTS[number];
30
-
31
- export const PROMOTION_EVIDENCE = [
32
- 'schema_valid_rubric',
33
- 'schema_valid_fixture_set',
34
- 'sandbox_run_receipt',
35
- 'known_bad_rejection',
36
- 'known_good_acceptance',
37
- ] as const;
38
- export type PromotionEvidence = typeof PROMOTION_EVIDENCE[number];
39
-
40
- export interface ValidationCheck {
41
- check_id: string;
42
- description: string;
43
- severity: CheckSeverity;
44
- evaluation: CheckEvaluation;
45
- evidence_required: string[];
46
- }
47
-
48
- export interface ValidationRubric {
49
- kind: 'validation';
50
- rubric_id: string;
51
- rubric_version: string;
52
- domain: string;
53
- artifact_type: string;
54
- title: string;
55
- summary: string;
56
- checks: ValidationCheck[];
57
- known_bad_fixtures: string[];
58
- known_good_fixtures: string[];
59
- evidence_requirements: string[];
60
- owner_principal_ref: string;
61
- status: RubricStatus;
62
- }
63
-
64
- export interface ConformanceRubric {
65
- kind: 'conformance';
66
- rubric_id: string;
67
- rubric_version: string;
68
- protocol_surface: string;
69
- schema_refs: string[];
70
- forbidden_content_classes: ContentClass[];
71
- citation_rules: string[];
72
- sanitizer_expectations: string[];
73
- failure_semantics: {
74
- on_reject: 'block' | 'warn';
75
- on_uncertain: 'warn' | 'require_review';
76
- };
77
- known_bad_fixtures: string[];
78
- known_good_fixtures: string[];
79
- owner_principal_ref: string;
80
- status: RubricStatus;
81
- }
82
-
83
- export type Rubric = ValidationRubric | ConformanceRubric;
84
-
85
- export interface RubricFixture {
86
- fixture_id: string;
87
- fixture_version: string;
88
- content_class: ContentClass;
89
- fixture_kind: FixtureKind;
90
- artifact_type: string;
91
- input_ref: string;
92
- expected: RubricOutcome;
93
- rationale: string;
94
- input?: unknown;
95
- }
96
-
97
- export interface RubricFixtureSet {
98
- fixture_set_id: string;
99
- fixture_set_version: string;
100
- fixtures: RubricFixture[];
101
- }
102
-
103
- export interface SandboxResult {
104
- fixture_id: string;
105
- expected: RubricOutcome;
106
- actual: RubricOutcome;
107
- reason: string;
108
- }
109
-
110
- export interface SandboxRunReceipt {
111
- schema: 'sandbox-run-receipt.v0';
112
- receipt_id: string;
113
- rubric_id: string;
114
- rubric_version: string;
115
- fixture_set_ref: string;
116
- results: SandboxResult[];
117
- summary: {
118
- total_fixtures: number;
119
- known_bad_rejected: number;
120
- known_good_accepted: number;
121
- unexpected_outcomes: number;
122
- };
123
- run_at: string;
124
- run_by: string;
125
- run_environment: RunEnvironment;
126
- }
127
-
128
- export interface PromotionPolicy {
129
- policy_id: string;
130
- policy_version: string;
131
- applies_to: {
132
- domains: string[];
133
- artifact_types: string[];
134
- protocol_surfaces: string[];
135
- };
136
- proposer_actor: string;
137
- reviewer_set: string[];
138
- required_evidence: PromotionEvidence[];
139
- grant_ref: string | null;
140
- activation_status: 'proposed' | 'ready_for_grant' | 'active' | 'rejected' | 'retired';
141
- rollback_path?: {
142
- retire_command: string;
143
- replacement_policy_ref: string | null;
144
- };
145
- }
146
-
147
- export interface PromotionReadiness {
148
- rubric_id: string;
149
- rubric_version: string;
150
- evidence: Record<PromotionEvidence, boolean>;
151
- missing: string[];
152
- grant_ref: string | null;
153
- recommendation: 'blocked' | 'ready_for_grant' | 'ready_to_activate';
154
- authority_boundary: {
155
- can_activate_rubric: false;
156
- requires_operator_principal_grant: true;
157
- };
158
- }