@intentius/chant 0.100.0 → 0.101.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/build.d.ts +6 -0
- package/dist/build.d.ts.map +1 -1
- package/dist/cli/build-options.d.ts +2 -0
- package/dist/cli/build-options.d.ts.map +1 -1
- package/dist/cli/commands/build.d.ts.map +1 -1
- package/dist/cli/commands/import.d.ts.map +1 -1
- package/dist/cli/handlers/fan-out.d.ts.map +1 -1
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/mcp/workspace-tools.d.ts +8 -0
- package/dist/cli/mcp/workspace-tools.d.ts.map +1 -1
- package/dist/cli/registry.d.ts +22 -0
- package/dist/cli/registry.d.ts.map +1 -1
- package/dist/config.d.ts +11 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/lexicon.d.ts +24 -1
- package/dist/lexicon.d.ts.map +1 -1
- package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
- package/dist/lifecycle/plan-digest.d.ts +26 -5
- package/dist/lifecycle/plan-digest.d.ts.map +1 -1
- package/dist/lint/config.d.ts +4 -4
- package/dist/op/activities/activity-contracts.d.ts +1 -0
- package/dist/op/activities/activity-contracts.d.ts.map +1 -1
- package/dist/op/activities/propose-upgrade.d.ts +2 -0
- package/dist/op/activities/propose-upgrade.d.ts.map +1 -1
- package/dist/op/index.d.ts +1 -1
- package/dist/op/index.d.ts.map +1 -1
- package/dist/serializer.d.ts +8 -0
- package/dist/serializer.d.ts.map +1 -1
- package/dist/telemetry-attribution.d.ts +77 -0
- package/dist/telemetry-attribution.d.ts.map +1 -0
- package/dist/workspace/agent-cli.d.ts +83 -0
- package/dist/workspace/agent-cli.d.ts.map +1 -0
- package/dist/workspace/changes-cli.d.ts.map +1 -1
- package/dist/workspace/changes.d.ts +8 -1
- package/dist/workspace/changes.d.ts.map +1 -1
- package/dist/workspace/checks/links.d.ts +1 -0
- package/dist/workspace/checks/links.d.ts.map +1 -1
- package/dist/workspace/checks/live.d.ts +40 -0
- package/dist/workspace/checks/live.d.ts.map +1 -0
- package/dist/workspace/checks.d.ts +21 -2
- package/dist/workspace/checks.d.ts.map +1 -1
- package/dist/workspace/compose-graph.d.ts +63 -0
- package/dist/workspace/compose-graph.d.ts.map +1 -1
- package/dist/workspace/decide.d.ts +1 -1
- package/dist/workspace/decide.d.ts.map +1 -1
- package/dist/workspace/declaration.d.ts +32 -0
- package/dist/workspace/declaration.d.ts.map +1 -1
- package/dist/workspace/declaration.schema.json +138 -3
- package/dist/workspace/export-cli.d.ts +12 -0
- package/dist/workspace/export-cli.d.ts.map +1 -0
- package/dist/workspace/export.d.ts +145 -0
- package/dist/workspace/export.d.ts.map +1 -0
- package/dist/workspace/graph-cli.d.ts.map +1 -1
- package/dist/workspace/import.d.ts +73 -0
- package/dist/workspace/import.d.ts.map +1 -0
- package/dist/workspace/kinds.d.ts +6 -2
- package/dist/workspace/kinds.d.ts.map +1 -1
- package/dist/workspace/lineage-adopt-cli.d.ts +15 -0
- package/dist/workspace/lineage-adopt-cli.d.ts.map +1 -0
- package/dist/workspace/lineage-adopt.d.ts +106 -0
- package/dist/workspace/lineage-adopt.d.ts.map +1 -0
- package/dist/workspace/lineage-check.d.ts +9 -2
- package/dist/workspace/lineage-check.d.ts.map +1 -1
- package/dist/workspace/lineage-cli.d.ts +6 -1
- package/dist/workspace/lineage-cli.d.ts.map +1 -1
- package/dist/workspace/lineage-hash-index.d.ts +110 -0
- package/dist/workspace/lineage-hash-index.d.ts.map +1 -0
- package/dist/workspace/lineage-init.d.ts +10 -0
- package/dist/workspace/lineage-init.d.ts.map +1 -1
- package/dist/workspace/lineage-lock.d.ts +147 -0
- package/dist/workspace/lineage-lock.d.ts.map +1 -1
- package/dist/workspace/lineage-migrations.d.ts +15 -3
- package/dist/workspace/lineage-migrations.d.ts.map +1 -1
- package/dist/workspace/lineage-provenance.d.ts +18 -0
- package/dist/workspace/lineage-provenance.d.ts.map +1 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts +2 -0
- package/dist/workspace/lineage-upgrade-cli.d.ts.map +1 -1
- package/dist/workspace/lineage-upgrade.d.ts +30 -1
- package/dist/workspace/lineage-upgrade.d.ts.map +1 -1
- package/dist/workspace/lineage-versions.d.ts +116 -0
- package/dist/workspace/lineage-versions.d.ts.map +1 -0
- package/dist/workspace/links.d.ts +31 -5
- package/dist/workspace/links.d.ts.map +1 -1
- package/dist/workspace/ls-generated.d.ts +37 -0
- package/dist/workspace/ls-generated.d.ts.map +1 -0
- package/dist/workspace/ls.d.ts +4 -0
- package/dist/workspace/ls.d.ts.map +1 -1
- package/dist/workspace/member-commands.d.ts.map +1 -1
- package/dist/workspace/nested-graph.d.ts +56 -0
- package/dist/workspace/nested-graph.d.ts.map +1 -0
- package/dist/workspace/nesting.d.ts +21 -0
- package/dist/workspace/nesting.d.ts.map +1 -0
- package/dist/workspace/pin-cli.d.ts +10 -0
- package/dist/workspace/pin-cli.d.ts.map +1 -0
- package/dist/workspace/pin-integrity.d.ts +51 -0
- package/dist/workspace/pin-integrity.d.ts.map +1 -0
- package/dist/workspace/reason-codes.d.ts +26 -2
- package/dist/workspace/reason-codes.d.ts.map +1 -1
- package/dist/workspace/record-sessions.d.ts +7 -11
- package/dist/workspace/record-sessions.d.ts.map +1 -1
- package/dist/workspace/records-cli.d.ts +30 -1
- package/dist/workspace/records-cli.d.ts.map +1 -1
- package/dist/workspace/records-close.d.ts +5 -2
- package/dist/workspace/records-close.d.ts.map +1 -1
- package/dist/workspace/records-write.d.ts +22 -4
- package/dist/workspace/records-write.d.ts.map +1 -1
- package/dist/workspace/records.d.ts +43 -5
- package/dist/workspace/records.d.ts.map +1 -1
- package/dist/workspace/returns.d.ts +129 -0
- package/dist/workspace/returns.d.ts.map +1 -0
- package/dist/workspace/status-gates.d.ts.map +1 -1
- package/dist/workspace/template-manifest.d.ts +11 -3
- package/dist/workspace/template-manifest.d.ts.map +1 -1
- package/dist/workspace/trust/attestor.d.ts +8 -0
- package/dist/workspace/trust/attestor.d.ts.map +1 -1
- package/dist/workspace/trust/dsse.d.ts +58 -0
- package/dist/workspace/trust/dsse.d.ts.map +1 -0
- package/dist/workspace/trust/evidence-cli.d.ts +66 -0
- package/dist/workspace/trust/evidence-cli.d.ts.map +1 -0
- package/dist/workspace/trust/evidence.d.ts +93 -0
- package/dist/workspace/trust/evidence.d.ts.map +1 -0
- package/dist/workspace/trust/policy.d.ts +54 -1
- package/dist/workspace/trust/policy.d.ts.map +1 -1
- package/dist/workspace/trust/provenance.d.ts +21 -1
- package/dist/workspace/trust/provenance.d.ts.map +1 -1
- package/dist/workspace/trust/rotation.d.ts +132 -0
- package/dist/workspace/trust/rotation.d.ts.map +1 -0
- package/dist/workspace/trust/seal.d.ts.map +1 -1
- package/dist/workspace/trust/signers-cli.d.ts +49 -0
- package/dist/workspace/trust/signers-cli.d.ts.map +1 -0
- package/dist/workspace/trust/ssh-commit.d.ts +12 -0
- package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
- package/dist/workspace/trust/test-repo.d.ts +13 -0
- package/dist/workspace/trust/test-repo.d.ts.map +1 -1
- package/dist/workspace/trust/verify.d.ts +15 -0
- package/dist/workspace/trust/verify.d.ts.map +1 -1
- package/dist/workspace/work-evidence.d.ts +1 -1
- package/dist/workspace/work-evidence.d.ts.map +1 -1
- package/dist/workspace/write-scope.d.ts +199 -0
- package/dist/workspace/write-scope.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/build.ts +8 -0
- package/src/cli/build-options.ts +4 -1
- package/src/cli/commands/build.ts +2 -0
- package/src/cli/commands/import-live.test.ts +69 -1
- package/src/cli/commands/import.ts +48 -22
- package/src/cli/handlers/fan-out.test.ts +6 -6
- package/src/cli/handlers/fan-out.ts +2 -1
- package/src/cli/handlers/graph.test.ts +42 -0
- package/src/cli/handlers/graph.ts +22 -0
- package/src/cli/handlers/operator.ts +1 -1
- package/src/cli/main.test.ts +32 -0
- package/src/cli/main.ts +103 -6
- package/src/cli/mcp/workspace-tools.test.ts +1 -1
- package/src/cli/mcp/workspace-tools.ts +33 -2
- package/src/cli/registry.ts +22 -0
- package/src/cli/serve-mcp-workspace.test.ts +1 -1
- package/src/codegen/release-wiring.test.ts +5 -1
- package/src/components/fan-out-output.test.ts +1 -1
- package/src/components/fan-out.test.ts +1 -1
- package/src/components/promote.test.ts +1 -1
- package/src/config.ts +12 -0
- package/src/content-digest.test.ts +2 -2
- package/src/lexicon.ts +25 -1
- package/src/lifecycle/gate-ledger.test.ts +14 -0
- package/src/lifecycle/gate-ledger.ts +2 -1
- package/src/lifecycle/plan-digest.test.ts +54 -3
- package/src/lifecycle/plan-digest.ts +38 -8
- package/src/op/activities/activity-contracts.ts +1 -0
- package/src/op/activities/propose-upgrade.ts +8 -5
- package/src/op/gate-approval.test.ts +17 -0
- package/src/op/gate.ts +3 -3
- package/src/op/index.ts +1 -1
- package/src/serializer.ts +9 -0
- package/src/telemetry-attribution.test.ts +91 -0
- package/src/telemetry-attribution.ts +145 -0
- package/src/workspace/agent-cli.ts +134 -0
- package/src/workspace/agent.schema.json +356 -0
- package/src/workspace/behold-kinds.test.ts +1 -1
- package/src/workspace/changes-cli.ts +5 -0
- package/src/workspace/changes.schema.json +179 -1
- package/src/workspace/changes.ts +58 -4
- package/src/workspace/check-live.test.ts +192 -0
- package/src/workspace/check.schema.json +64 -0
- package/src/workspace/checks/links.ts +23 -2
- package/src/workspace/checks/live.ts +113 -0
- package/src/workspace/checks.test.ts +2 -2
- package/src/workspace/checks.ts +20 -3
- package/src/workspace/compose-graph.test.ts +47 -0
- package/src/workspace/compose-graph.ts +120 -3
- package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +7 -1
- package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +5 -0
- package/src/workspace/declaration.schema.json +138 -3
- package/src/workspace/declaration.ts +106 -1
- package/src/workspace/declared-kinds.test.ts +33 -0
- package/src/workspace/evidence.schema.json +279 -0
- package/src/workspace/export-cli.ts +180 -0
- package/src/workspace/export-import.test.ts +282 -0
- package/src/workspace/export.ts +486 -0
- package/src/workspace/graph-cli.ts +47 -1
- package/src/workspace/graph-contract.test.ts +89 -4
- package/src/workspace/graph.schema.json +206 -1
- package/src/workspace/import.ts +325 -0
- package/src/workspace/kinds.test.ts +5 -5
- package/src/workspace/kinds.ts +25 -3
- package/src/workspace/lineage-adopt-cli.ts +103 -0
- package/src/workspace/lineage-adopt.test.ts +552 -0
- package/src/workspace/lineage-adopt.ts +452 -0
- package/src/workspace/lineage-check.ts +26 -5
- package/src/workspace/lineage-cli.ts +12 -2
- package/src/workspace/lineage-hash-index.ts +305 -0
- package/src/workspace/lineage-init.test.ts +9 -0
- package/src/workspace/lineage-init.ts +29 -9
- package/src/workspace/lineage-lock.ts +54 -0
- package/src/workspace/lineage-migrations.test.ts +27 -0
- package/src/workspace/lineage-migrations.ts +44 -14
- package/src/workspace/lineage-provenance.ts +40 -0
- package/src/workspace/lineage-upgrade-cli.ts +9 -5
- package/src/workspace/lineage-upgrade.test.ts +92 -1
- package/src/workspace/lineage-upgrade.ts +111 -16
- package/src/workspace/lineage-versions.ts +348 -0
- package/src/workspace/links.test.ts +121 -2
- package/src/workspace/links.ts +97 -6
- package/src/workspace/ls-contract.test.ts +84 -1
- package/src/workspace/ls-generated.ts +111 -0
- package/src/workspace/ls.schema.json +19 -0
- package/src/workspace/ls.ts +8 -1
- package/src/workspace/member-commands.ts +3 -1
- package/src/workspace/nested-graph.test.ts +176 -0
- package/src/workspace/nested-graph.ts +169 -0
- package/src/workspace/nesting.ts +37 -0
- package/src/workspace/pin-cli.test.ts +71 -0
- package/src/workspace/pin-cli.ts +57 -0
- package/src/workspace/pin-integrity.test.ts +121 -0
- package/src/workspace/pin-integrity.ts +104 -0
- package/src/workspace/points-write.schema.json +1 -0
- package/src/workspace/read-contract.test.ts +24 -0
- package/src/workspace/reason-codes.test.ts +10 -0
- package/src/workspace/reason-codes.ts +29 -2
- package/src/workspace/record-sessions.ts +12 -14
- package/src/workspace/records-amend.schema.json +4 -0
- package/src/workspace/records-cli.ts +93 -12
- package/src/workspace/records-close.schema.json +6 -2
- package/src/workspace/records-close.ts +10 -2
- package/src/workspace/records-formats.test.ts +13 -5
- package/src/workspace/records-new.schema.json +4 -0
- package/src/workspace/records-review.schema.json +4 -0
- package/src/workspace/records-sessions.test.ts +23 -6
- package/src/workspace/records-write.test.ts +65 -8
- package/src/workspace/records-write.ts +70 -6
- package/src/workspace/records.schema.json +55 -3
- package/src/workspace/records.ts +96 -5
- package/src/workspace/returns.ts +328 -0
- package/src/workspace/signers.schema.json +206 -0
- package/src/workspace/status-gates.ts +2 -1
- package/src/workspace/template-manifest.ts +22 -4
- package/src/workspace/trust/attestor.ts +15 -0
- package/src/workspace/trust/dsse.ts +134 -0
- package/src/workspace/trust/evidence-cli.ts +195 -0
- package/src/workspace/trust/evidence.test.ts +241 -0
- package/src/workspace/trust/evidence.ts +207 -0
- package/src/workspace/trust/policy.ts +110 -3
- package/src/workspace/trust/provenance.ts +41 -4
- package/src/workspace/trust/record-seal.test.ts +1 -1
- package/src/workspace/trust/rotation.test.ts +258 -0
- package/src/workspace/trust/rotation.ts +336 -0
- package/src/workspace/trust/seal.ts +11 -0
- package/src/workspace/trust/signers-cli.ts +178 -0
- package/src/workspace/trust/ssh-commit.ts +53 -4
- package/src/workspace/trust/test-repo.ts +18 -0
- package/src/workspace/trust/trust.test.ts +8 -2
- package/src/workspace/trust/verify-cli.ts +1 -0
- package/src/workspace/trust/verify.ts +22 -0
- package/src/workspace/work-evidence.schema.json +4 -0
- package/src/workspace/write-scope.test.ts +340 -0
- package/src/workspace/write-scope.ts +448 -0
|
@@ -107,6 +107,35 @@ export interface ChangesPolicy {
|
|
|
107
107
|
ignore: string[];
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
+
/** The core principal classes (#2524 D5). */
|
|
111
|
+
export const PRINCIPAL_CLASSES = ["human", "agent", "runner", "service"] as const;
|
|
112
|
+
export type PrincipalClass = (typeof PRINCIPAL_CLASSES)[number];
|
|
113
|
+
|
|
114
|
+
/** How a record is written, as the records commands name it (#2548). */
|
|
115
|
+
export const WRITE_VERBS = ["new", "amend", "review", "close"] as const;
|
|
116
|
+
export type WriteVerb = (typeof WRITE_VERBS)[number];
|
|
117
|
+
|
|
118
|
+
/** One principal class's entry in the declaration's `writeScope` (#2548, ws-067). */
|
|
119
|
+
export interface ClassScope {
|
|
120
|
+
/** The members whose files the class may write, or null for every path. Always null for the agent class: a session writes its own member. */
|
|
121
|
+
members: string[] | null;
|
|
122
|
+
/** Kind name to the verbs the class may write it with, or null for every kind in reach with every verb. */
|
|
123
|
+
records: Record<string, WriteVerb[]> | null;
|
|
124
|
+
pointer: string;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** The declaration's `writeScope` block: an entry per restricted class. A class with no entry is not restricted. */
|
|
128
|
+
export type WriteScope = Partial<Record<PrincipalClass, ClassScope>>;
|
|
129
|
+
|
|
130
|
+
/** An agent session the declaration names (#2524 D20, #2548): bound to one member. */
|
|
131
|
+
export interface AgentDeclaration {
|
|
132
|
+
name: string;
|
|
133
|
+
member: string;
|
|
134
|
+
/** Principals that write only as this session. */
|
|
135
|
+
principals: string[];
|
|
136
|
+
pointer: string;
|
|
137
|
+
}
|
|
138
|
+
|
|
110
139
|
export interface MemberRole {
|
|
111
140
|
name: string;
|
|
112
141
|
/** Relative to the member's directory, or null for the whole member. */
|
|
@@ -135,6 +164,8 @@ export interface LinkDeclaration {
|
|
|
135
164
|
output: string;
|
|
136
165
|
/** The link kind as written, or null for the default (`output`). */
|
|
137
166
|
kind: string | null;
|
|
167
|
+
/** A telemetry link's OTLP protocol as written (#2558), or null. */
|
|
168
|
+
protocol: string | null;
|
|
138
169
|
/** The link's JSON Pointer in the file, for messages. */
|
|
139
170
|
pointer: string;
|
|
140
171
|
}
|
|
@@ -292,6 +323,8 @@ export interface Member {
|
|
|
292
323
|
box: BoxDeclaration | null;
|
|
293
324
|
upstream: string | null;
|
|
294
325
|
because: string | null;
|
|
326
|
+
/** Whether `chant workspace export` takes the member (#2552, D10). False when the entry does not say. */
|
|
327
|
+
travel: boolean;
|
|
295
328
|
suppress: Suppression[];
|
|
296
329
|
/** The entry's JSON Pointer in the file, for messages. */
|
|
297
330
|
pointer: string;
|
|
@@ -338,6 +371,10 @@ export interface Declaration {
|
|
|
338
371
|
hosts: Host[];
|
|
339
372
|
/** The forward coverage check's policy (#2773), or null when the declaration has no `changes` block. */
|
|
340
373
|
changes: ChangesPolicy | null;
|
|
374
|
+
/** Who may write what (#2548), or null when the declaration has no `writeScope` block. */
|
|
375
|
+
writeScope: WriteScope | null;
|
|
376
|
+
/** The agent sessions, in file order (#2548). */
|
|
377
|
+
agents: AgentDeclaration[];
|
|
341
378
|
/** The file, relative to the workspace root's tree (`chant.workspace.json` or `.jsonc`). */
|
|
342
379
|
file: string;
|
|
343
380
|
}
|
|
@@ -543,10 +580,11 @@ export function parseDeclaration(text: string, file: string, reader: string = re
|
|
|
543
580
|
handWritten: g.handWritten ? { because: (g.handWritten as { because: string }).because } : null,
|
|
544
581
|
pointer: `${pointer}/generated/${j}`,
|
|
545
582
|
}));
|
|
546
|
-
const links = ((e.links as { member: string; output: string; kind?: string }[] | undefined) ?? []).map((l, j) => ({
|
|
583
|
+
const links = ((e.links as { member: string; output: string; kind?: string; protocol?: string }[] | undefined) ?? []).map((l, j) => ({
|
|
547
584
|
member: l.member,
|
|
548
585
|
output: l.output,
|
|
549
586
|
kind: l.kind ?? null,
|
|
587
|
+
protocol: l.protocol ?? null,
|
|
550
588
|
pointer: `${pointer}/links/${j}`,
|
|
551
589
|
}));
|
|
552
590
|
const records = recordKindsOf(e.records, e.dir as string, e.name as string, `${pointer}/records`);
|
|
@@ -566,6 +604,7 @@ export function parseDeclaration(text: string, file: string, reader: string = re
|
|
|
566
604
|
box,
|
|
567
605
|
upstream: (e.upstream as string | undefined) ?? null,
|
|
568
606
|
because: (e.because as string | undefined) ?? null,
|
|
607
|
+
travel: e.travel === true,
|
|
569
608
|
suppress,
|
|
570
609
|
pointer,
|
|
571
610
|
};
|
|
@@ -673,6 +712,8 @@ export function parseDeclaration(text: string, file: string, reader: string = re
|
|
|
673
712
|
}
|
|
674
713
|
|
|
675
714
|
const hosts = hostsOf(obj, members, at);
|
|
715
|
+
const writeScope = writeScopeOf(obj.writeScope, members, at);
|
|
716
|
+
const agents = agentsOf(obj.agents, members, at);
|
|
676
717
|
|
|
677
718
|
const pins = ((obj.pins as Record<string, string>[] | undefined) ?? []).map((p) => ({
|
|
678
719
|
package: p.package ?? null,
|
|
@@ -695,10 +736,74 @@ export function parseDeclaration(text: string, file: string, reader: string = re
|
|
|
695
736
|
diagrams: ownDiagrams,
|
|
696
737
|
hosts,
|
|
697
738
|
changes: changesOf(obj.changes as Record<string, unknown> | undefined),
|
|
739
|
+
writeScope,
|
|
740
|
+
agents,
|
|
698
741
|
file,
|
|
699
742
|
};
|
|
700
743
|
}
|
|
701
744
|
|
|
745
|
+
/**
|
|
746
|
+
* The `writeScope` block, already validated, with the rule the schema can't
|
|
747
|
+
* say (#2548): a member it names is a declared member.
|
|
748
|
+
*/
|
|
749
|
+
function writeScopeOf(raw: unknown, members: Member[], at: (pointer: string, key?: boolean) => ErrorLocation): WriteScope | null {
|
|
750
|
+
if (raw === undefined) return null;
|
|
751
|
+
const block = raw as Partial<Record<PrincipalClass, { members?: "*" | string[]; records?: Record<string, WriteVerb[]> }>>;
|
|
752
|
+
const out: WriteScope = {};
|
|
753
|
+
for (const cls of PRINCIPAL_CLASSES) {
|
|
754
|
+
const entry = block[cls];
|
|
755
|
+
if (entry === undefined) continue;
|
|
756
|
+
const pointer = `/writeScope/${cls}`;
|
|
757
|
+
const list = entry.members === undefined || entry.members === "*" ? null : [...entry.members];
|
|
758
|
+
for (const [i, name] of (list ?? []).entries()) {
|
|
759
|
+
if (!members.some((m) => m.name === name)) {
|
|
760
|
+
throw new WorkspaceReadError(
|
|
761
|
+
"declaration-invalid",
|
|
762
|
+
`writeScope.${cls} names the member ${JSON.stringify(name)}, which the declaration does not declare; declared members: ${members.map((m) => m.name).join(", ") || "none"}`,
|
|
763
|
+
at(`${pointer}/members/${i}`),
|
|
764
|
+
);
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
const records = entry.records === undefined ? null : Object.fromEntries(Object.entries(entry.records).map(([k, v]) => [k, [...v]]));
|
|
768
|
+
out[cls] = { members: list, records, pointer };
|
|
769
|
+
}
|
|
770
|
+
return out;
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
/**
|
|
774
|
+
* The `agents` list, already validated, with the rules the schema can't say
|
|
775
|
+
* (#2548): names are unique, each names a declared member, and a principal
|
|
776
|
+
* is listed by one session at most.
|
|
777
|
+
*/
|
|
778
|
+
function agentsOf(raw: unknown, members: Member[], at: (pointer: string, key?: boolean) => ErrorLocation): AgentDeclaration[] {
|
|
779
|
+
const agents = ((raw as { name: string; member: string; principals?: string[] }[] | undefined) ?? []).map((a, i) => ({
|
|
780
|
+
name: a.name,
|
|
781
|
+
member: a.member,
|
|
782
|
+
principals: [...(a.principals ?? [])],
|
|
783
|
+
pointer: `/agents/${i}`,
|
|
784
|
+
}));
|
|
785
|
+
const byName = new Map<string, AgentDeclaration>();
|
|
786
|
+
const byPrincipal = new Map<string, AgentDeclaration>();
|
|
787
|
+
for (const a of agents) {
|
|
788
|
+
const first = byName.get(a.name);
|
|
789
|
+
if (first) throw new WorkspaceReadError("declaration-invalid", `the agent name ${JSON.stringify(a.name)} is already used by the agent at ${first.pointer}`, at(`${a.pointer}/name`));
|
|
790
|
+
byName.set(a.name, a);
|
|
791
|
+
if (!members.some((m) => m.name === a.member)) {
|
|
792
|
+
throw new WorkspaceReadError(
|
|
793
|
+
"declaration-invalid",
|
|
794
|
+
`agent ${a.name} is bound to ${JSON.stringify(a.member)}, which is not a declared member; an agent session is bound to one member, not an example group`,
|
|
795
|
+
at(`${a.pointer}/member`),
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
for (const [i, p] of a.principals.entries()) {
|
|
799
|
+
const other = byPrincipal.get(p);
|
|
800
|
+
if (other) throw new WorkspaceReadError("declaration-invalid", `the principal ${JSON.stringify(p)} is already listed by agent ${other.name}`, at(`${a.pointer}/principals/${i}`));
|
|
801
|
+
byPrincipal.set(p, a);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
return agents;
|
|
805
|
+
}
|
|
806
|
+
|
|
702
807
|
/** The `changes` block, already validated, with its defaults (#2773). */
|
|
703
808
|
function changesOf(block: Record<string, unknown> | undefined): ChangesPolicy | null {
|
|
704
809
|
if (block === undefined) return null;
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* `--kind` is not given, while `--kind` still overrides.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { createHash } from "node:crypto";
|
|
8
9
|
import { readFileSync } from "node:fs";
|
|
9
10
|
import { join } from "node:path";
|
|
10
11
|
import { afterAll, afterEach, beforeEach, describe, expect, test, vi } from "vitest";
|
|
@@ -199,6 +200,38 @@ describe("records without --kind reads every declared kind (#2680)", () => {
|
|
|
199
200
|
expect(printed.kinds.map((k: { records: { id: string }[] }) => k.records.map((r) => r.id))).toEqual([["ref-001"], ["ref-001"]]);
|
|
200
201
|
});
|
|
201
202
|
|
|
203
|
+
test("--current adds the spec: the current records of spec: true kinds, with the files they pin (#2546, #2524 D20)", async () => {
|
|
204
|
+
const server = "export const port = 8080;\n";
|
|
205
|
+
const sha256 = createHash("sha256").update(server).digest("hex");
|
|
206
|
+
// ref-004 supersedes ref-001 and pins the app's server file; the note kind is not part of the spec.
|
|
207
|
+
const ref004 = REF_001.replace('id: "ref-001"', 'id: "ref-004"')
|
|
208
|
+
.replace("supersedes: []", 'supersedes:\n - decision: "ref-001"')
|
|
209
|
+
.replace(/^evidence:\n/m, `evidence:\n - title: "The server"\n path: "app/server.mjs"\n sha256: "${sha256}"\n`);
|
|
210
|
+
const root = workspace({
|
|
211
|
+
"decisions/ref-004-how-the-app-is-deployed-now.md": ref004,
|
|
212
|
+
"design/notes/note.kind.mjs": NOTE_KIND.replace("spec: true", "spec: false"),
|
|
213
|
+
});
|
|
214
|
+
expect(await run(root, "--current", "--json")).toBe(0);
|
|
215
|
+
const printed = JSON.parse(out.join("\n"));
|
|
216
|
+
expectValid(printed);
|
|
217
|
+
expect(printed.kinds.map((k: { kind: { spec: boolean } }) => k.kind.spec)).toEqual([true, false]);
|
|
218
|
+
expect(printed.spec.kinds).toEqual([{ member: null, path: "decisions/decision.kind.mjs", name: null }]);
|
|
219
|
+
expect(printed.spec.records).toEqual([
|
|
220
|
+
expect.objectContaining({
|
|
221
|
+
kind: "decision",
|
|
222
|
+
id: "ref-004",
|
|
223
|
+
path: "decisions/ref-004-how-the-app-is-deployed-now.md",
|
|
224
|
+
state: "decided",
|
|
225
|
+
valid: true,
|
|
226
|
+
assets: [{ path: "app/server.mjs", sha256, actual: sha256, state: "pinned" }],
|
|
227
|
+
}),
|
|
228
|
+
]);
|
|
229
|
+
// Without --current there is no spec: superseded records are still listed under kinds.
|
|
230
|
+
out.length = 0;
|
|
231
|
+
expect(await run(root, "--json")).toBe(0);
|
|
232
|
+
expect(JSON.parse(out.join("\n")).spec).toBeUndefined();
|
|
233
|
+
});
|
|
234
|
+
|
|
202
235
|
test("a kind that can't be read is listed with its error, the others are read, and the exit code is 1", async () => {
|
|
203
236
|
const root = workspace({}, [{ kind: "gone.kind.mjs" }]);
|
|
204
237
|
expect(await run(root, "--json")).toBe(1);
|
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://intentius.io/chant/schemas/workspace/evidence/v1/evidence.schema.json",
|
|
4
|
+
"title": "chant workspace evidence output",
|
|
5
|
+
"description": "What `chant workspace evidence verify --json` prints (#2553), and what a refused `chant workspace evidence sign --json` prints: runner evidence, an in-toto statement over record hashes in a DSSE envelope, checked offline against the runner keys `.chant/trust.json` lists at the base revision. Version 1 of the read contract. Readers ignore fields they do not know; a field is only ever added within a version. The error codes are a closed list, each in the one closed list of `reason-codes.ts`.",
|
|
6
|
+
"oneOf": [
|
|
7
|
+
{
|
|
8
|
+
"$ref": "#/$defs/result"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
"$ref": "#/$defs/failure"
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"$defs": {
|
|
15
|
+
"result": {
|
|
16
|
+
"type": "object",
|
|
17
|
+
"description": "A runner key the policy at base lists signed the envelope. `status` says whether the records still hash as signed; it exits 1 when they don't.",
|
|
18
|
+
"required": [
|
|
19
|
+
"$schema",
|
|
20
|
+
"contract",
|
|
21
|
+
"chant",
|
|
22
|
+
"at",
|
|
23
|
+
"ok",
|
|
24
|
+
"status",
|
|
25
|
+
"runner",
|
|
26
|
+
"class",
|
|
27
|
+
"keyid",
|
|
28
|
+
"statement",
|
|
29
|
+
"subjects"
|
|
30
|
+
],
|
|
31
|
+
"properties": {
|
|
32
|
+
"$schema": {
|
|
33
|
+
"const": "https://intentius.io/chant/schemas/workspace/evidence/v1/evidence.schema.json"
|
|
34
|
+
},
|
|
35
|
+
"contract": {
|
|
36
|
+
"const": 1
|
|
37
|
+
},
|
|
38
|
+
"chant": {
|
|
39
|
+
"type": "string"
|
|
40
|
+
},
|
|
41
|
+
"at": {
|
|
42
|
+
"type": "string",
|
|
43
|
+
"pattern": "^(?:[0-9a-f]{40}|[0-9a-f]{64})$",
|
|
44
|
+
"description": "The commit the subjects were compared at."
|
|
45
|
+
},
|
|
46
|
+
"ok": {
|
|
47
|
+
"const": true
|
|
48
|
+
},
|
|
49
|
+
"status": {
|
|
50
|
+
"enum": [
|
|
51
|
+
"current",
|
|
52
|
+
"stale"
|
|
53
|
+
],
|
|
54
|
+
"description": "current when every subject hashes the same at `at`; stale otherwise. Stale evidence is never reused."
|
|
55
|
+
},
|
|
56
|
+
"runner": {
|
|
57
|
+
"type": "string",
|
|
58
|
+
"description": "The runner principal whose key signed."
|
|
59
|
+
},
|
|
60
|
+
"class": {
|
|
61
|
+
"enum": [
|
|
62
|
+
"runner",
|
|
63
|
+
"service"
|
|
64
|
+
],
|
|
65
|
+
"description": "A CI runner or a service, as `.chant/trust.json` lists it."
|
|
66
|
+
},
|
|
67
|
+
"keyid": {
|
|
68
|
+
"type": "string",
|
|
69
|
+
"description": "The ssh SHA256 fingerprint of the key that signed."
|
|
70
|
+
},
|
|
71
|
+
"statement": {
|
|
72
|
+
"$ref": "#/$defs/statement"
|
|
73
|
+
},
|
|
74
|
+
"subjects": {
|
|
75
|
+
"type": "array",
|
|
76
|
+
"items": {
|
|
77
|
+
"$ref": "#/$defs/subject"
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"statement": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"description": "The in-toto Statement v1 the runner signed.",
|
|
85
|
+
"required": [
|
|
86
|
+
"_type",
|
|
87
|
+
"subject",
|
|
88
|
+
"predicateType",
|
|
89
|
+
"predicate"
|
|
90
|
+
],
|
|
91
|
+
"properties": {
|
|
92
|
+
"_type": {
|
|
93
|
+
"const": "https://in-toto.io/Statement/v1"
|
|
94
|
+
},
|
|
95
|
+
"subject": {
|
|
96
|
+
"type": "array",
|
|
97
|
+
"items": {
|
|
98
|
+
"type": "object",
|
|
99
|
+
"required": [
|
|
100
|
+
"name",
|
|
101
|
+
"digest"
|
|
102
|
+
],
|
|
103
|
+
"properties": {
|
|
104
|
+
"name": {
|
|
105
|
+
"type": "string"
|
|
106
|
+
},
|
|
107
|
+
"digest": {
|
|
108
|
+
"type": "object",
|
|
109
|
+
"required": [
|
|
110
|
+
"sha256"
|
|
111
|
+
],
|
|
112
|
+
"properties": {
|
|
113
|
+
"sha256": {
|
|
114
|
+
"type": "string",
|
|
115
|
+
"pattern": "^[0-9a-f]{64}$"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"predicateType": {
|
|
123
|
+
"const": "https://intentius.io/chant/runner-evidence/v1"
|
|
124
|
+
},
|
|
125
|
+
"predicate": {
|
|
126
|
+
"type": "object",
|
|
127
|
+
"required": [
|
|
128
|
+
"runner",
|
|
129
|
+
"commit",
|
|
130
|
+
"tree",
|
|
131
|
+
"check"
|
|
132
|
+
],
|
|
133
|
+
"properties": {
|
|
134
|
+
"runner": {
|
|
135
|
+
"type": "string"
|
|
136
|
+
},
|
|
137
|
+
"commit": {
|
|
138
|
+
"type": "string",
|
|
139
|
+
"pattern": "^(?:[0-9a-f]{40}|[0-9a-f]{64})$"
|
|
140
|
+
},
|
|
141
|
+
"tree": {
|
|
142
|
+
"type": "string",
|
|
143
|
+
"pattern": "^(?:[0-9a-f]{40}|[0-9a-f]{64})$"
|
|
144
|
+
},
|
|
145
|
+
"check": {
|
|
146
|
+
"type": "string",
|
|
147
|
+
"description": "The check's id, as given to --check-id."
|
|
148
|
+
},
|
|
149
|
+
"claim": {
|
|
150
|
+
"type": "object",
|
|
151
|
+
"required": [
|
|
152
|
+
"sha256"
|
|
153
|
+
],
|
|
154
|
+
"properties": {
|
|
155
|
+
"sha256": {
|
|
156
|
+
"type": "string",
|
|
157
|
+
"pattern": "^[0-9a-f]{64}$"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"environment": {
|
|
162
|
+
"type": "object",
|
|
163
|
+
"required": [
|
|
164
|
+
"sha256"
|
|
165
|
+
],
|
|
166
|
+
"properties": {
|
|
167
|
+
"sha256": {
|
|
168
|
+
"type": "string",
|
|
169
|
+
"pattern": "^[0-9a-f]{64}$"
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
},
|
|
177
|
+
"subject": {
|
|
178
|
+
"type": "object",
|
|
179
|
+
"required": [
|
|
180
|
+
"name",
|
|
181
|
+
"signed",
|
|
182
|
+
"now",
|
|
183
|
+
"matches"
|
|
184
|
+
],
|
|
185
|
+
"properties": {
|
|
186
|
+
"name": {
|
|
187
|
+
"type": "string",
|
|
188
|
+
"description": "The record's path from the repository root."
|
|
189
|
+
},
|
|
190
|
+
"signed": {
|
|
191
|
+
"type": "string",
|
|
192
|
+
"pattern": "^[0-9a-f]{64}$"
|
|
193
|
+
},
|
|
194
|
+
"now": {
|
|
195
|
+
"oneOf": [
|
|
196
|
+
{
|
|
197
|
+
"type": "null"
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
"type": "string",
|
|
201
|
+
"pattern": "^[0-9a-f]{64}$"
|
|
202
|
+
}
|
|
203
|
+
],
|
|
204
|
+
"description": "The record's hash at `at`, or null when it is not there."
|
|
205
|
+
},
|
|
206
|
+
"matches": {
|
|
207
|
+
"type": "boolean"
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
"failure": {
|
|
212
|
+
"type": "object",
|
|
213
|
+
"description": "The evidence was refused, or could not be checked. Exits 1.",
|
|
214
|
+
"required": [
|
|
215
|
+
"$schema",
|
|
216
|
+
"contract",
|
|
217
|
+
"chant",
|
|
218
|
+
"at",
|
|
219
|
+
"error"
|
|
220
|
+
],
|
|
221
|
+
"properties": {
|
|
222
|
+
"$schema": {
|
|
223
|
+
"const": "https://intentius.io/chant/schemas/workspace/evidence/v1/evidence.schema.json"
|
|
224
|
+
},
|
|
225
|
+
"contract": {
|
|
226
|
+
"const": 1
|
|
227
|
+
},
|
|
228
|
+
"chant": {
|
|
229
|
+
"type": "string"
|
|
230
|
+
},
|
|
231
|
+
"at": {
|
|
232
|
+
"oneOf": [
|
|
233
|
+
{
|
|
234
|
+
"type": "null"
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
"type": "string",
|
|
238
|
+
"pattern": "^(?:[0-9a-f]{40}|[0-9a-f]{64})$"
|
|
239
|
+
}
|
|
240
|
+
]
|
|
241
|
+
},
|
|
242
|
+
"error": {
|
|
243
|
+
"type": "object",
|
|
244
|
+
"required": [
|
|
245
|
+
"code",
|
|
246
|
+
"message"
|
|
247
|
+
],
|
|
248
|
+
"properties": {
|
|
249
|
+
"code": {
|
|
250
|
+
"enum": [
|
|
251
|
+
"not-a-git-repository",
|
|
252
|
+
"revision-unknown",
|
|
253
|
+
"trust-policy-unreadable",
|
|
254
|
+
"envelope-unreadable",
|
|
255
|
+
"envelope-invalid",
|
|
256
|
+
"envelope-untrusted",
|
|
257
|
+
"evidence-payload-type",
|
|
258
|
+
"evidence-statement-invalid",
|
|
259
|
+
"evidence-runner-mismatch",
|
|
260
|
+
"runner-key-invalid",
|
|
261
|
+
"runner-key-is-signer",
|
|
262
|
+
"runner-key-unlisted",
|
|
263
|
+
"kind-unreadable",
|
|
264
|
+
"kind-invalid",
|
|
265
|
+
"schema-unreadable",
|
|
266
|
+
"schema-id-mismatch",
|
|
267
|
+
"schema-invalid",
|
|
268
|
+
"location-missing"
|
|
269
|
+
]
|
|
270
|
+
},
|
|
271
|
+
"message": {
|
|
272
|
+
"type": "string"
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `chant workspace export [<member>...] [--to <member>] [--param name=value] [--dry-run] [--json]`,
|
|
3
|
+
* `chant workspace import [<dir>] [--remove] [--dry-run] [--json]` and
|
|
4
|
+
* `chant workspace admit <return id> [--note <text>] [--dry-run] [--json]` (#2552).
|
|
5
|
+
*
|
|
6
|
+
* See ./export.ts, ./import.ts and ./returns.ts.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { formatError, formatInfo, formatSuccess, formatWarning } from "../cli/format";
|
|
11
|
+
import type { CommandContext } from "../cli/registry";
|
|
12
|
+
import { findWorkspaceRoot } from "../project-root";
|
|
13
|
+
import { WorkspaceReadError } from "./declaration";
|
|
14
|
+
import { LockError } from "./lineage-lock";
|
|
15
|
+
import { parseParamArgs } from "./template-manifest";
|
|
16
|
+
|
|
17
|
+
const EXPORT_USAGE = "chant workspace export [<member>[,<member>...]] [--to <export member>] [--param name=value] [--dry-run] [--json]";
|
|
18
|
+
const IMPORT_USAGE = "chant workspace import [<dir>] [--remove] [--dry-run] [--json]";
|
|
19
|
+
const ADMIT_USAGE = "chant workspace admit <return id> [--note <text>] [--dry-run] [--json]";
|
|
20
|
+
|
|
21
|
+
function workspaceRoot(usage: string): string | undefined {
|
|
22
|
+
const found = findWorkspaceRoot(process.cwd());
|
|
23
|
+
if (!found) {
|
|
24
|
+
console.error(formatError({ message: "no chant.workspace.json here or above", hint: usage }));
|
|
25
|
+
return undefined;
|
|
26
|
+
}
|
|
27
|
+
return found.dir;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function fail(err: unknown, usage: string): number {
|
|
31
|
+
if (err instanceof WorkspaceReadError) {
|
|
32
|
+
console.error(formatError({ message: `${err.code}: ${err.describe()}`, hint: usage }));
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
if (err instanceof LockError) {
|
|
36
|
+
console.error(formatError({ message: err.message, hint: usage }));
|
|
37
|
+
return 1;
|
|
38
|
+
}
|
|
39
|
+
throw err;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export async function runWorkspaceExport(ctx: CommandContext): Promise<number> {
|
|
43
|
+
const { args } = ctx;
|
|
44
|
+
const root = workspaceRoot(EXPORT_USAGE);
|
|
45
|
+
if (!root) return 1;
|
|
46
|
+
const members = [args.extraPositional, args.extraPositional2]
|
|
47
|
+
.filter((x): x is string => x !== undefined)
|
|
48
|
+
.flatMap((x) => x.split(","))
|
|
49
|
+
.map((x) => x.trim())
|
|
50
|
+
.filter(Boolean);
|
|
51
|
+
try {
|
|
52
|
+
const { exportWorkspace } = await import("./export");
|
|
53
|
+
const r = await exportWorkspace({
|
|
54
|
+
root,
|
|
55
|
+
members,
|
|
56
|
+
to: args.migrateTo,
|
|
57
|
+
params: args.param?.length ? parseParamArgs(args.param) : undefined,
|
|
58
|
+
dryRun: args.dryRun,
|
|
59
|
+
});
|
|
60
|
+
if (args.json) {
|
|
61
|
+
console.log(JSON.stringify(r, null, 2));
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
const m = r.manifest;
|
|
65
|
+
console.log(`${m.id} ${m.whole ? "workspace" : "members"} ${m.members.join(", ")} -> ${r.member} (${r.dir})`);
|
|
66
|
+
console.log(` ${Object.keys(m.files).length} file(s), ${Object.keys(m.locks).length} lock(s)${m.dirs.length ? `, record directories ${m.dirs.join(", ")}` : ""}`);
|
|
67
|
+
if (r.switched.length > 0) console.log(` host values switched in: ${r.switched.join(", ")}`);
|
|
68
|
+
const d = m.dropped;
|
|
69
|
+
const left = [
|
|
70
|
+
...d.links.map((l) => `link ${l.member} -> ${l.to}`),
|
|
71
|
+
...d.agents.map((a) => `agent ${a}`),
|
|
72
|
+
...d.pins.map((p) => `pin ${p}`),
|
|
73
|
+
...d.records.map((k) => `record kind ${k}`),
|
|
74
|
+
...d.diagrams.map((x) => `diagram ${x}`),
|
|
75
|
+
];
|
|
76
|
+
if (left.length > 0) console.log(` left out of the export's declaration: ${left.join("; ")}`);
|
|
77
|
+
if (m.from.dirty) console.error(formatWarning({ message: "exported paths have uncommitted changes; the export records HEAD, which does not hold them" }));
|
|
78
|
+
if (r.written) console.error(formatSuccess(`wrote the export into ${r.dir}. It is a workspace of its own; copy it where it goes, and bring it back with chant workspace import.`));
|
|
79
|
+
else console.error(formatInfo("--dry-run: nothing written"));
|
|
80
|
+
return 0;
|
|
81
|
+
} catch (err) {
|
|
82
|
+
return fail(err, EXPORT_USAGE);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function runWorkspaceImport(ctx: CommandContext): Promise<number> {
|
|
87
|
+
const { args } = ctx;
|
|
88
|
+
if (args.extraPositional2) {
|
|
89
|
+
console.error(formatError({ message: `unexpected argument: ${args.extraPositional2}`, hint: IMPORT_USAGE }));
|
|
90
|
+
return 1;
|
|
91
|
+
}
|
|
92
|
+
const root = workspaceRoot(IMPORT_USAGE);
|
|
93
|
+
if (!root) return 1;
|
|
94
|
+
try {
|
|
95
|
+
const { importWorkspace } = await import("./import");
|
|
96
|
+
const r = await importWorkspace({ root, from: args.extraPositional ? join(process.cwd(), args.extraPositional) : undefined, remove: args.remove, dryRun: args.dryRun });
|
|
97
|
+
const refused = r.outside.length > 0 || r.conflicts.length > 0;
|
|
98
|
+
if (args.json) {
|
|
99
|
+
console.log(JSON.stringify(r, null, 2));
|
|
100
|
+
return refused ? 1 : 0;
|
|
101
|
+
}
|
|
102
|
+
console.log(`${r.export} from ${r.from}`);
|
|
103
|
+
for (const p of r.written) console.log(` write ${p}${r.hostValues.includes(p) ? " (host values back)" : ""}`);
|
|
104
|
+
for (const p of r.removed) console.log(` remove ${p}`);
|
|
105
|
+
for (const p of r.locks) console.log(` lock ${p}`);
|
|
106
|
+
for (const c of r.conflicts) console.log(` conflict ${c.path}: ${c.reason}`);
|
|
107
|
+
for (const p of r.outside) console.log(` outside ${p}`);
|
|
108
|
+
if (r.outside.length > 0) {
|
|
109
|
+
console.error(formatError({ message: `the copy holds ${r.outside.length} file(s) outside the members that went with the export; import never writes into another member, so nothing was written` }));
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
112
|
+
if (r.conflicts.length > 0) {
|
|
113
|
+
console.error(formatError({ message: `${r.conflicts.length} conflict(s): nothing was written. Resolve them in the copy or here, then import again` }));
|
|
114
|
+
return 1;
|
|
115
|
+
}
|
|
116
|
+
if (r.return) {
|
|
117
|
+
const proved = Object.values(r.return.paths).filter((p) => p.origin).length;
|
|
118
|
+
console.log(` return ${r.return.id}: ${proved} file(s) carry the commit they were made in${r.return.signers.length ? `, signed by ${r.return.signers.map((s) => `${s.principal} (${s.fingerprint || "key"})`).join(", ")}` : ""}`);
|
|
119
|
+
}
|
|
120
|
+
if (!r.applied) {
|
|
121
|
+
console.error(formatInfo("--dry-run: nothing written"));
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
if (!r.return) console.error(formatInfo("the copy holds no change since the export"));
|
|
125
|
+
if (r.member) console.log(` member ${r.member.name} ${r.member.action}${r.member.note ? `: ${r.member.note}` : ""}`);
|
|
126
|
+
if (r.returnRecord) {
|
|
127
|
+
console.error(
|
|
128
|
+
formatSuccess(
|
|
129
|
+
`imported, and recorded the return in ${r.returnRecord}. Review and commit it. Work signed by keys the policy at base does not list reads as attested-unverifiable-here until an admin runs chant workspace admit ${r.return!.id} and merges that change.`,
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
return 0;
|
|
134
|
+
} catch (err) {
|
|
135
|
+
return fail(err, IMPORT_USAGE);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function runWorkspaceAdmit(ctx: CommandContext): Promise<number> {
|
|
140
|
+
const { args } = ctx;
|
|
141
|
+
const id = args.extraPositional;
|
|
142
|
+
if (!id || args.extraPositional2) {
|
|
143
|
+
console.error(formatError({ message: "admit takes one return id, such as ret-0123456789ab", hint: ADMIT_USAGE }));
|
|
144
|
+
return 1;
|
|
145
|
+
}
|
|
146
|
+
const root = workspaceRoot(ADMIT_USAGE);
|
|
147
|
+
if (!root) return 1;
|
|
148
|
+
try {
|
|
149
|
+
const { readFileSync, existsSync } = await import("node:fs");
|
|
150
|
+
const { RETURNS_DIR, admitReturn, parseReturn } = await import("./returns");
|
|
151
|
+
const { gitTopOf } = await import("./export");
|
|
152
|
+
const file = join(root, RETURNS_DIR, `${id}.json`);
|
|
153
|
+
if (!/^ret-[0-9a-f]{12}$/.test(id) || !existsSync(file)) throw new LockError(`no return ${id} in ${RETURNS_DIR}`);
|
|
154
|
+
const ret = parseReturn(readFileSync(file, "utf-8"), file);
|
|
155
|
+
const repo = gitTopOf(root);
|
|
156
|
+
if (!repo) throw new LockError("admit writes .chant/trust.json at the repository root, and this workspace is not in a git repository");
|
|
157
|
+
const r = admitReturn(repo, ret, { note: args.note, dryRun: args.dryRun });
|
|
158
|
+
if (args.json) {
|
|
159
|
+
console.log(JSON.stringify(r, null, 2));
|
|
160
|
+
return 0;
|
|
161
|
+
}
|
|
162
|
+
if (r.added.length === 0) {
|
|
163
|
+
console.error(formatInfo(`every signer of ${id} is already admitted in ${r.path}`));
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
166
|
+
for (const s of r.added) console.log(` admit ${s.principal} ${s.key.slice(0, 40)}...`);
|
|
167
|
+
if (!r.written) {
|
|
168
|
+
console.error(formatInfo("--dry-run: nothing written"));
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
console.error(
|
|
172
|
+
formatSuccess(
|
|
173
|
+
`added ${r.added.length} signer(s) for ${id} to ${r.path}. Check each principal, then commit it signed: the file is policy, so the admission counts once an admin's signed commit is merged to the base branch.`,
|
|
174
|
+
),
|
|
175
|
+
);
|
|
176
|
+
return 0;
|
|
177
|
+
} catch (err) {
|
|
178
|
+
return fail(err, ADMIT_USAGE);
|
|
179
|
+
}
|
|
180
|
+
}
|