@intentius/chant 0.85.0 → 0.87.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.
Files changed (129) hide show
  1. package/dist/cli/main.d.ts.map +1 -1
  2. package/dist/cli/registry.d.ts +13 -1
  3. package/dist/cli/registry.d.ts.map +1 -1
  4. package/dist/lifecycle/gate-ledger.d.ts +13 -0
  5. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  6. package/dist/workspace/__fixtures__/sessions.d.ts +23 -0
  7. package/dist/workspace/__fixtures__/sessions.d.ts.map +1 -0
  8. package/dist/workspace/checks/records.d.ts +1 -0
  9. package/dist/workspace/checks/records.d.ts.map +1 -1
  10. package/dist/workspace/checks.d.ts +4 -0
  11. package/dist/workspace/checks.d.ts.map +1 -1
  12. package/dist/workspace/composites.d.ts +14 -1
  13. package/dist/workspace/composites.d.ts.map +1 -1
  14. package/dist/workspace/conformance/index.d.ts +211 -0
  15. package/dist/workspace/conformance/index.d.ts.map +1 -0
  16. package/dist/workspace/conformance/vitest.d.ts +11 -0
  17. package/dist/workspace/conformance/vitest.d.ts.map +1 -0
  18. package/dist/workspace/declaration.d.ts +28 -0
  19. package/dist/workspace/declaration.d.ts.map +1 -1
  20. package/dist/workspace/declaration.schema.json +40 -0
  21. package/dist/workspace/declared-kinds.d.ts +43 -0
  22. package/dist/workspace/declared-kinds.d.ts.map +1 -0
  23. package/dist/workspace/graph-cli.d.ts +11 -0
  24. package/dist/workspace/graph-cli.d.ts.map +1 -1
  25. package/dist/workspace/intent-cli.d.ts +2 -1
  26. package/dist/workspace/intent-cli.d.ts.map +1 -1
  27. package/dist/workspace/intent-joins.d.ts +45 -8
  28. package/dist/workspace/intent-joins.d.ts.map +1 -1
  29. package/dist/workspace/intent.d.ts +71 -7
  30. package/dist/workspace/intent.d.ts.map +1 -1
  31. package/dist/workspace/ls.d.ts +31 -1
  32. package/dist/workspace/ls.d.ts.map +1 -1
  33. package/dist/workspace/reason-codes.d.ts +47 -4
  34. package/dist/workspace/reason-codes.d.ts.map +1 -1
  35. package/dist/workspace/record-sessions.d.ts +51 -0
  36. package/dist/workspace/record-sessions.d.ts.map +1 -0
  37. package/dist/workspace/record-source.d.ts +2 -0
  38. package/dist/workspace/record-source.d.ts.map +1 -1
  39. package/dist/workspace/records-cli.d.ts +71 -4
  40. package/dist/workspace/records-cli.d.ts.map +1 -1
  41. package/dist/workspace/records-since.d.ts +90 -0
  42. package/dist/workspace/records-since.d.ts.map +1 -0
  43. package/dist/workspace/records-write.d.ts +171 -0
  44. package/dist/workspace/records-write.d.ts.map +1 -0
  45. package/dist/workspace/records.d.ts +244 -15
  46. package/dist/workspace/records.d.ts.map +1 -1
  47. package/dist/workspace/runtimes.d.ts +60 -0
  48. package/dist/workspace/runtimes.d.ts.map +1 -0
  49. package/dist/workspace/status-gates.d.ts +90 -0
  50. package/dist/workspace/status-gates.d.ts.map +1 -0
  51. package/dist/workspace/status.d.ts +17 -0
  52. package/dist/workspace/status.d.ts.map +1 -1
  53. package/dist/workspace/trust/seal.d.ts +85 -0
  54. package/dist/workspace/trust/seal.d.ts.map +1 -0
  55. package/dist/workspace/trust/ssh-commit.d.ts +7 -0
  56. package/dist/workspace/trust/ssh-commit.d.ts.map +1 -1
  57. package/dist/workspace/work.d.ts +56 -0
  58. package/dist/workspace/work.d.ts.map +1 -0
  59. package/package.json +19 -1
  60. package/src/cli/main.ts +55 -3
  61. package/src/cli/registry.ts +13 -1
  62. package/src/lifecycle/gate-ledger.ts +14 -0
  63. package/src/workspace/__fixtures__/sessions.ts +66 -0
  64. package/src/workspace/checks/records.ts +19 -0
  65. package/src/workspace/checks.test.ts +2 -0
  66. package/src/workspace/checks.ts +7 -1
  67. package/src/workspace/composites.schema.json +65 -3
  68. package/src/workspace/composites.test.ts +95 -5
  69. package/src/workspace/composites.ts +28 -7
  70. package/src/workspace/conformance/__fixture__/app/package.json +7 -0
  71. package/src/workspace/conformance/__fixture__/app/src/server.mjs +29 -0
  72. package/src/workspace/conformance/__fixture__/decisions/decision.kind.mjs +32 -0
  73. package/src/workspace/conformance/__fixture__/decisions/decision.schema.json +376 -0
  74. package/src/workspace/conformance/__fixture__/decisions/fix-001-how-the-app-is-deployed.md +40 -0
  75. package/src/workspace/conformance/__fixture__/delivery/chant.config.ts +7 -0
  76. package/src/workspace/conformance/__fixture__/delivery/lexicon/index.ts +26 -0
  77. package/src/workspace/conformance/__fixture__/delivery/package.json +7 -0
  78. package/src/workspace/conformance/__fixture__/delivery/src/app.component.ts +14 -0
  79. package/src/workspace/conformance/__fixture__/delivery/src/app.ts +4 -0
  80. package/src/workspace/conformance/conformance.test.ts +149 -0
  81. package/src/workspace/conformance/index.mjs +31 -0
  82. package/src/workspace/conformance/index.ts +453 -0
  83. package/src/workspace/conformance/vitest.ts +62 -0
  84. package/src/workspace/declaration.schema.json +40 -0
  85. package/src/workspace/declaration.ts +62 -0
  86. package/src/workspace/declared-kinds.test.ts +321 -0
  87. package/src/workspace/declared-kinds.ts +76 -0
  88. package/src/workspace/graph-cli.ts +8 -0
  89. package/src/workspace/intent-cli.ts +29 -6
  90. package/src/workspace/intent-gaps.test.ts +217 -0
  91. package/src/workspace/intent-joins.test.ts +60 -0
  92. package/src/workspace/intent-joins.ts +71 -19
  93. package/src/workspace/intent.schema.json +304 -7
  94. package/src/workspace/intent.test.ts +99 -0
  95. package/src/workspace/intent.ts +365 -46
  96. package/src/workspace/ls.schema.json +34 -0
  97. package/src/workspace/ls.ts +69 -4
  98. package/src/workspace/read-contract.test.ts +30 -9
  99. package/src/workspace/reason-codes.test.ts +16 -4
  100. package/src/workspace/reason-codes.ts +55 -4
  101. package/src/workspace/record-assets.test.ts +3 -1
  102. package/src/workspace/record-sessions.ts +105 -0
  103. package/src/workspace/record-source.ts +14 -5
  104. package/src/workspace/records-amend.schema.json +167 -0
  105. package/src/workspace/records-cli.ts +308 -19
  106. package/src/workspace/records-contract.test.ts +57 -2
  107. package/src/workspace/records-formats.test.ts +640 -0
  108. package/src/workspace/records-new.schema.json +158 -0
  109. package/src/workspace/records-quorum.test.ts +196 -0
  110. package/src/workspace/records-review.schema.json +227 -0
  111. package/src/workspace/records-sessions.test.ts +108 -0
  112. package/src/workspace/records-since.schema.json +193 -0
  113. package/src/workspace/records-since.test.ts +174 -0
  114. package/src/workspace/records-since.ts +259 -0
  115. package/src/workspace/records-write-contract.test.ts +125 -0
  116. package/src/workspace/records-write.test.ts +373 -0
  117. package/src/workspace/records-write.ts +765 -0
  118. package/src/workspace/records.schema.json +202 -9
  119. package/src/workspace/records.ts +700 -41
  120. package/src/workspace/runtimes.ts +107 -0
  121. package/src/workspace/status-contract.test.ts +163 -0
  122. package/src/workspace/status-gates.ts +215 -0
  123. package/src/workspace/status.schema.json +69 -3
  124. package/src/workspace/status.ts +35 -2
  125. package/src/workspace/trust/seal.test.ts +232 -0
  126. package/src/workspace/trust/seal.ts +195 -0
  127. package/src/workspace/trust/ssh-commit.ts +2 -2
  128. package/src/workspace/work.test.ts +390 -0
  129. package/src/workspace/work.ts +163 -0
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Sealed verdicts (#2687, part of #2547): a review entry signed by the
3
+ * principal it names, so the quorum counts it only when that principal
4
+ * signed it.
5
+ *
6
+ * A seal is a detached ssh signature, the same mechanism as the ssh-commit
7
+ * attestor (./ssh-commit.ts): `ssh-keygen -Y sign` to make it, and
8
+ * `ssh-keygen -Y verify` against the signers read at base to check it. It is
9
+ * made in its own namespace, `chant-review`, so neither a commit signature
10
+ * nor a signer-set signature can stand in for one.
11
+ *
12
+ * The signed bytes are the record id, the verdict's digest, the verdict, the
13
+ * reviewer and the date, each on its own line with no final newline. The
14
+ * digest is the record's text digest (`recordTextDigest`), so the seal binds
15
+ * the verdict to the text it judged. The seal lives inside the reviews block,
16
+ * which the digest leaves out, so sealing a verdict never moves the digest.
17
+ */
18
+ import type { SealCode } from "../records.js";
19
+ import { type TrustPolicy } from "./policy.js";
20
+ /** The ssh signature namespace of a verdict seal. */
21
+ export declare const REVIEW_SEAL_NAMESPACE = "chant-review";
22
+ /** A seal as a review entry holds it. */
23
+ export interface VerdictSeal {
24
+ /** The principal who signed: the reviewer, as the signers file names them. */
25
+ signer: string;
26
+ /** The signing key's fingerprint, such as `SHA256:...`. Reported, never trusted: the signature is the proof. */
27
+ key: string;
28
+ /** The armored ssh signature. */
29
+ signature: string;
30
+ }
31
+ /** What a verdict's seal establishes. */
32
+ export interface SealCheck {
33
+ /** true: the seal verifies for the reviewer against the signers at base. false: it is missing under an active policy, or it fails. null: nothing here can say. */
34
+ attested: boolean | null;
35
+ /** Why it is not attested. Absent when `attested` is true. */
36
+ code?: SealCode;
37
+ message: string;
38
+ /** The fingerprint of the key that made the signature, when the signature was checked. */
39
+ key?: string;
40
+ }
41
+ /** The verdict a seal covers. */
42
+ export interface SealedVerdict {
43
+ record: string | null;
44
+ reviewer: string;
45
+ verdict: string;
46
+ on: unknown;
47
+ digest: string | null;
48
+ seal: unknown;
49
+ }
50
+ /** The bytes a verdict seal signs. */
51
+ export declare function reviewSealPayload(record: string, digest: string, verdict: string, reviewer: string, on: string): Buffer;
52
+ /**
53
+ * Check a verdict's seal. With a signers file active at base, it verifies
54
+ * against the keys listed there for the reviewer. With none, a seal present
55
+ * is checked for integrity only (`ssh-keygen -Y check-novalidate`): nothing
56
+ * says whose key it is.
57
+ */
58
+ export declare function checkVerdictSeal(policy: TrustPolicy, v: SealedVerdict): SealCheck;
59
+ /** Why a seal could not be made. */
60
+ export declare class SealError extends Error {
61
+ constructor(message: string);
62
+ }
63
+ /**
64
+ * The key `--sign` names: the file given, or with none, git's
65
+ * `user.signingkey` when `gpg.format` is `ssh`, as `git commit -S` reads it.
66
+ * A literal public key (`key::ssh-...`, or `ssh-...`) signs through the ssh
67
+ * agent holding its private half. Returns the file to pass to ssh-keygen,
68
+ * and a cleanup for a temporary one.
69
+ */
70
+ export declare function resolveSigningKey(sign: string | true, cwd: string): {
71
+ file: string;
72
+ cleanup: () => void;
73
+ };
74
+ /**
75
+ * Seal a verdict with the key in `keyFile` (a private key, or a public key
76
+ * whose private half the ssh agent holds). Throws a {@link SealError}.
77
+ */
78
+ export declare function sealVerdict(keyFile: string, v: {
79
+ record: string;
80
+ digest: string;
81
+ verdict: string;
82
+ reviewer: string;
83
+ on: string;
84
+ }): VerdictSeal;
85
+ //# sourceMappingURL=seal.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seal.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/seal.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAC3C,OAAO,EAAE,KAAK,WAAW,EAAE,MAAM,UAAU,CAAC;AAG5C,qDAAqD;AACrD,eAAO,MAAM,qBAAqB,iBAAiB,CAAC;AAEpD,yCAAyC;AACzC,MAAM,WAAW,WAAW;IAC1B,8EAA8E;IAC9E,MAAM,EAAE,MAAM,CAAC;IACf,gHAAgH;IAChH,GAAG,EAAE,MAAM,CAAC;IACZ,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,yCAAyC;AACzC,MAAM,WAAW,SAAS;IACxB,kKAAkK;IAClK,QAAQ,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,8DAA8D;IAC9D,IAAI,CAAC,EAAE,QAAQ,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,0FAA0F;IAC1F,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,iCAAiC;AACjC,MAAM,WAAW,aAAa;IAC5B,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,OAAO,CAAC;CACf;AAED,sCAAsC;AACtC,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,GAAG,MAAM,CAEvH;AAMD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,EAAE,aAAa,GAAG,SAAS,CA2BjF;AAyBD,oCAAoC;AACpC,qBAAa,SAAU,SAAQ,KAAK;gBACtB,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,EAAE,GAAG,EAAE,MAAM,GAAG;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,IAAI,CAAA;CAAE,CAuBzG;AAMD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG,WAAW,CAmB9I"}
@@ -25,6 +25,13 @@ export interface SignedCommit {
25
25
  * continuation lines, to get the bytes it signed.
26
26
  */
27
27
  export declare function splitSignedCommit(raw: Buffer, header?: string): SignedCommit;
28
+ /** Run ssh-keygen; `missing` is true when it is not installed. Shared with the verdict seals (./seal.ts). */
29
+ export declare function sshKeygen(args: string[], input?: Buffer): {
30
+ status: number | null;
31
+ stdout: string;
32
+ stderr: string;
33
+ missing: boolean;
34
+ };
28
35
  /**
29
36
  * Check a detached ssh signature over `payload` in `namespace` against
30
37
  * `signers`. Shared with the rotation check (#2553), which signs signer sets
@@ -1 +1 @@
1
- {"version":3,"file":"ssh-commit.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/ssh-commit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAsC,cAAc,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAwB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAI7D,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAW,GAAG,YAAY,CAsB9E;AAmBD;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAuBjG;AAED,eAAO,MAAM,iBAAiB,EAAE,cAW/B,CAAC"}
1
+ {"version":3,"file":"ssh-commit.d.ts","sourceRoot":"","sources":["../../../src/workspace/trust/ssh-commit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,OAAO,KAAK,EAAsC,cAAc,EAAE,MAAM,YAAY,CAAC;AACrF,OAAO,EAAwB,KAAK,MAAM,EAAE,MAAM,UAAU,CAAC;AAI7D,uEAAuE;AACvE,MAAM,WAAW,YAAY;IAC3B,sFAAsF;IACtF,OAAO,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAW,GAAG,YAAY,CAsB9E;AAUD,6GAA6G;AAC7G,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,CAIrI;AAID;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,OAAO,EAAE,MAAM,EAAE,EACjB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,SAAS,EAAE,MAAM,GAChB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAuBjG;AAED,eAAO,MAAM,iBAAiB,EAAE,cAW/B,CAAC"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Work items on read (#2683): a record kind with a `work` block.
3
+ *
4
+ * A work item is a record in the workspace, a git-tracked file with its
5
+ * dependencies written inside it, that people and agents share as one queue
6
+ * with no server. Most work items come from a gap the intent graph already
7
+ * reports (`source.finding`), given an id, an owner and a lifecycle.
8
+ *
9
+ * `records.ts` calls {@link applyWork} after it has read a work kind's
10
+ * records, before it sets `valid` and applies `--current`. This module reads
11
+ * the decision kind the work kind names, at the same revision, and gives each
12
+ * work record:
13
+ *
14
+ * - `ready`: its state is the kind's open state, every need is done, and the
15
+ * record is valid and not superseded;
16
+ * - `blockedBy`: each need that is not done, with its state;
17
+ * - `implements`: each decision it names, with that decision's state;
18
+ *
19
+ * and each decision `implementedBy`, the work records naming it. The
20
+ * warnings are closed codes. `work-done-gap-open` is not raised here: it
21
+ * takes a walk of the item's region, which `graph --intent` makes and
22
+ * `records` asks it for (#2686). It never writes a record.
23
+ */
24
+ import { type LoadedRecordKind, type ReadRecordsOptions, type RecordEntry } from "./records.js";
25
+ /** Why a work record carries a warning. Closed, like the record warning codes. */
26
+ export declare const WORK_WARNING_CODES: readonly ["work-needs-unknown", "work-implements-unknown", "work-needs-cycle", "work-implements-undecided", "work-done-unpinned", "work-closed-without-date", "work-done-gap-open"];
27
+ export type WorkWarningCode = (typeof WORK_WARNING_CODES)[number];
28
+ /** A link from a work record to another record, with that record's state now. */
29
+ export interface WorkLink {
30
+ id: string;
31
+ /** The linked record's state, or null when no record has the id. */
32
+ state: string | null;
33
+ }
34
+ /** A decision as a work read lists it: its state and the work records implementing it. */
35
+ export interface DecisionWork {
36
+ id: string;
37
+ path: string;
38
+ state: string | null;
39
+ supersededBy: string | null;
40
+ implementedBy: WorkLink[];
41
+ }
42
+ /** The strings of a front-matter list, or none. */
43
+ export declare function idList(data: Record<string, unknown> | null, field: string): string[];
44
+ /**
45
+ * Whether `state` counts as decided in the decision kind: ranked above 0 by
46
+ * its approval ranks, or, for a kind without them, closed.
47
+ */
48
+ export declare function isDecided(kind: LoadedRecordKind["kind"], state: string | null): boolean;
49
+ /**
50
+ * Give each work record its links, ready and blockedBy, and its warnings, in
51
+ * place. Throws a `RecordReadError` when the decision kind can't be read.
52
+ */
53
+ export declare function applyWork(loaded: LoadedRecordKind, entries: RecordEntry[], options: ReadRecordsOptions): Promise<{
54
+ decisions: DecisionWork[];
55
+ }>;
56
+ //# sourceMappingURL=work.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"work.d.ts","sourceRoot":"","sources":["../../src/workspace/work.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,EAA+B,KAAK,gBAAgB,EAAE,KAAK,kBAAkB,EAAE,KAAK,WAAW,EAAE,MAAM,WAAW,CAAC;AAE1H,kFAAkF;AAClF,eAAO,MAAM,kBAAkB,qLAeW,CAAC;AAC3C,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAC;AAElE,iFAAiF;AACjF,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,oEAAoE;IACpE,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;CACtB;AAED,0FAA0F;AAC1F,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IACrB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,QAAQ,EAAE,CAAC;CAC3B;AAED,mDAAmD;AACnD,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,EAAE,CAGpF;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,gBAAgB,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,GAAG,OAAO,CAGvF;AAED;;;GAGG;AACH,wBAAsB,SAAS,CAAC,MAAM,EAAE,gBAAgB,EAAE,OAAO,EAAE,WAAW,EAAE,EAAE,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC;IAAE,SAAS,EAAE,YAAY,EAAE,CAAA;CAAE,CAAC,CAgFrJ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.85.0",
3
+ "version": "0.87.0",
4
4
  "description": "Declarative infrastructure-as-code toolkit — TypeScript on Node.js",
5
5
  "license": "Apache-2.0",
6
6
  "homepage": "https://intentius.io/chant",
@@ -50,6 +50,16 @@
50
50
  "default": "./src/cli/index.ts"
51
51
  },
52
52
  "./workspace/*.schema.json": "./src/workspace/*.schema.json",
53
+ "./workspace/conformance": {
54
+ "development": "./src/workspace/conformance/index.ts",
55
+ "types": "./dist/workspace/conformance/index.d.ts",
56
+ "default": "./src/workspace/conformance/index.mjs"
57
+ },
58
+ "./workspace/conformance/vitest": {
59
+ "development": "./src/workspace/conformance/vitest.ts",
60
+ "types": "./dist/workspace/conformance/vitest.d.ts",
61
+ "default": "./src/workspace/conformance/vitest.ts"
62
+ },
53
63
  "./cli/*": {
54
64
  "development": "./src/cli/*.ts",
55
65
  "types": "./dist/cli/*.d.ts",
@@ -104,6 +114,14 @@
104
114
  "devDependencies": {
105
115
  "@cdktn/hcl2json": "^0.24.0"
106
116
  },
117
+ "peerDependencies": {
118
+ "vitest": ">=2"
119
+ },
120
+ "peerDependenciesMeta": {
121
+ "vitest": {
122
+ "optional": true
123
+ }
124
+ },
107
125
  "overrides": {
108
126
  "esbuild": "^0.28.1"
109
127
  }
package/src/cli/main.ts CHANGED
@@ -375,6 +375,31 @@ export function parseArgs(args: string[]): ParsedArgs {
375
375
  if (!result.intent || result.intent.startsWith("-")) throw new Error("--intent needs a region: --intent <path[:start-end]>");
376
376
  } else if (arg === "--current") {
377
377
  result.current = true;
378
+ } else if (arg === "--set") {
379
+ // `chant workspace records amend <id> --set <file|->` (#2670)
380
+ result.set = args[++i];
381
+ if (!result.set || (result.set.startsWith("-") && result.set !== "-")) throw new Error("--set needs a JSON file, or - for standard input: --set <file|->");
382
+ } else if (arg === "--verdict") {
383
+ // `chant workspace records review <id> --verdict agree|dissent|abstain` (#2670)
384
+ result.verdict = args[++i];
385
+ if (!result.verdict || result.verdict.startsWith("-")) throw new Error("--verdict needs agree, dissent or abstain");
386
+ } else if (arg === "--by") {
387
+ // `chant workspace records review <id> --by <principal>` (#2670): whoever the caller says.
388
+ result.by = args[++i];
389
+ if (!result.by || result.by.startsWith("-")) throw new Error("--by needs the reviewer: --by <principal>");
390
+ } else if (arg === "--sign") {
391
+ // `chant workspace records review <id> --sign [<key file>]` (#2687): seal the verdict.
392
+ // With no key file, git's user.signingkey, as `git commit -S` reads it.
393
+ const next = args[i + 1];
394
+ result.sign = next !== undefined && !next.startsWith("-") ? args[++i] : true;
395
+ } else if (arg === "--session") {
396
+ // `chant workspace records review <id> --session <id>` (#2670)
397
+ result.session = args[++i];
398
+ if (!result.session || result.session.startsWith("-")) throw new Error("--session needs a session id: --session <id>");
399
+ } else if (arg === "--prefix") {
400
+ // `chant workspace records new <kind> --prefix <prefix>` (#2670): the id prefix to allocate under.
401
+ result.prefix = args[++i];
402
+ if (!result.prefix || result.prefix.startsWith("-")) throw new Error("--prefix needs an id prefix: --prefix <prefix>");
378
403
  } else if (arg === "--require") {
379
404
  // `chant workspace records|verify --require attested` (#2547)
380
405
  result.require = args[++i];
@@ -721,8 +746,9 @@ Workspace (level 1, #2524):
721
746
  beside it and marks the members whose digests differ.
722
747
  Read only; never fetches. --json prints the
723
748
  read-contract document
724
- workspace records --kind <kind file> [--current] [--at <rev>] [--base <rev>] [--require attested] [--json]
725
- Read the records a record kind locates, validated
749
+ workspace records [--kind <kind file>] [--current] [--at <rev>] [--base <rev>] [--require attested] [--json]
750
+ Without --kind, every record kind the declaration
751
+ names. Read the records a record kind locates, validated
726
752
  against its schema, with reason codes for invalid
727
753
  ones. --current leaves out superseded records; --at
728
754
  reads a commit's git objects. Needs no workspace file.
@@ -731,9 +757,34 @@ Workspace (level 1, #2524):
731
757
  --require attested exits 2 if any record is not
732
758
  attested. A pinned file that changed is a warning,
733
759
  asset-drift or asset-missing
760
+ workspace records [--kind <kind file>] --since <rev> [--at <rev>] [--json]
761
+ What changed in the records between <rev> and --at
762
+ (default: the working tree): new and removed records,
763
+ state transitions, new verdicts, new supersessions
764
+ and changed pins
734
765
  workspace records pin <path>
735
766
  Print the path from the workspace root and the
736
767
  sha256 of a file, for a decision's evidence pin
768
+ workspace records new [<kind file>] --from <file|-> [--prefix <prefix>] [--dry-run]
769
+ Write one new record in the kind's directory from
770
+ the JSON fields given, after validating them as
771
+ records would read them. Without a kind file, the one
772
+ kind the declaration names. Allocates the next id when
773
+ the fields hold none. Prints {path, id} as JSON and
774
+ never commits
775
+ workspace records amend <id> [--kind <kind file>] --set <file|-> [--dry-run]
776
+ Set top-level fields of one record. A closed record
777
+ never changes, and an approved one changes only its
778
+ state (upward), pins and reviews; anything else is
779
+ refused with amend-supersede-instead. Prints
780
+ {path, id, changed}
781
+ workspace records review <id> [--kind <kind file>] --verdict agree|dissent|abstain --by <principal> [--note <text>] [--session <id>] [--sign [<key file>]] [--dry-run]
782
+ Append a review to one record, dated and bound to
783
+ the digest of the record text. A dissent needs
784
+ --note. --sign seals it with an ssh key (git's
785
+ user.signingkey without a file); under a signers
786
+ file at base only a sealed verdict counts. Prints
787
+ {path, id, review}
737
788
  workspace verify [--base <rev>] [--head <rev>] [--require attested]
738
789
  Check the commits in base..head against the signers
739
790
  and roles read from base. A change to the signers file
@@ -784,7 +835,8 @@ Workspace (level 1, #2524):
784
835
  workspace graph --intent <path[:start-end]> [--at <rev>] [--kind <kind file>...] [--json]
785
836
  The intent graph over one region: the commits that
786
837
  touched it, the decisions whose constrains cover it,
787
- the artifacts they pin, and findings with closed codes
838
+ the artifacts they pin, and findings with closed codes.
839
+ Without --kind, every record kind the declaration names
788
840
 
789
841
  Lifecycle (alias: lc):
790
842
  lifecycle snapshot <env> Query API, save metadata to orphan branch
@@ -291,6 +291,18 @@ export interface ParsedArgs {
291
291
  intent?: string;
292
292
  /** `chant workspace records --current` (#2546): leave out records a closed record supersedes. */
293
293
  current?: boolean;
294
+ /** `chant workspace records amend <id> --set <file|->` (#2670): the JSON fields to set. */
295
+ set?: string;
296
+ /** `chant workspace records review <id> --verdict <v>` (#2670): agree, dissent or abstain. */
297
+ verdict?: string;
298
+ /** `chant workspace records review <id> --by <principal>` (#2670): the reviewer, as the caller names them. */
299
+ by?: string;
300
+ /** `chant workspace records review <id> --sign [<key file>]` (#2687): the key that seals the verdict, or true for git's user.signingkey. */
301
+ sign?: string | true;
302
+ /** `chant workspace records review <id> --session <id>` (#2670): the review session the verdict was given in. */
303
+ session?: string;
304
+ /** `chant workspace records new <kind> --prefix <prefix>` (#2670): the id prefix to allocate under. */
305
+ prefix?: string;
294
306
  /** `chant workspace records|verify --require attested` (#2547): the provenance level a gate requires. */
295
307
  require?: string;
296
308
  /**
@@ -416,7 +428,7 @@ export interface ParsedArgs {
416
428
  plan?: string;
417
429
  /** `chant operator log --op <name>` (#2029) — restrict the tick history to one ConvergeOp by name. Omitted, every discovered ConvergeOp's ticks are merged into one timeline. */
418
430
  op?: string;
419
- /** `chant operator log --since <iso>` (#2029) — only entries at or after this ISO-8601 instant. */
431
+ /** `chant operator log --since <iso>` (#2029) — only entries at or after this ISO-8601 instant. `chant workspace records --since <rev>` (#2673) — the revision to compare the records with. */
420
432
  since?: string;
421
433
  /** `chant operator log --limit <n>` (#2029) — keep only the newest n entries (still printed oldest-first). */
422
434
  limit?: number;
@@ -47,6 +47,9 @@ import type { GateApprover, GatePolicyDecision, ResolvedGateApproval } from "../
47
47
  import { readBlobFromPath, readPathSha, readBlobBySha, writeBlobToPath, RefCASConflictError } from "./git";
48
48
 
49
49
  const DIR = "_gates";
50
+
51
+ /** The directory on the `chant/lifecycle` branch that holds gate ledgers, under a member's prefix when it has one. */
52
+ export const GATES_DIR = DIR;
50
53
  const APPEND_RETRY_ATTEMPTS = 5;
51
54
 
52
55
  /**
@@ -350,7 +353,18 @@ export async function readGateLedger(
350
353
  ): Promise<{ resolutions: GateResolutionRecord[]; pending: PendingGateRecord[]; malformed: number }> {
351
354
  const content = await readBlobFromPath(DIR, filename(op), opts);
352
355
  if (!content) return { resolutions: [], pending: [], malformed: 0 };
356
+ return parseGateLedger(content);
357
+ }
353
358
 
359
+ /**
360
+ * Split the text of one gate ledger file into its two kinds of line, oldest
361
+ * first, the way {@link readGateLedger} reads it. Exported for a reader that
362
+ * fetches the file itself, such as `chant workspace status` (#2674), which
363
+ * reads every member's ledger by its full path from the workspace root.
364
+ */
365
+ export function parseGateLedger(
366
+ content: string,
367
+ ): { resolutions: GateResolutionRecord[]; pending: PendingGateRecord[]; malformed: number } {
354
368
  const lines = content.split("\n").map((l) => l.trim()).filter(Boolean);
355
369
  const resolutions: GateResolutionRecord[] = [];
356
370
  const pending: PendingGateRecord[] = [];
@@ -0,0 +1,66 @@
1
+ /**
2
+ * A repository with the reference workspace's decisions and design member,
3
+ * and helpers that write sessions and review entries into it, for the
4
+ * session and records --since tests (#2673).
5
+ */
6
+
7
+ import { cpSync, readFileSync, writeFileSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import { sessionSeal } from "../record-sessions";
10
+ import { commitAll, git, REPO, scratchDir } from "./contract-repo";
11
+
12
+ export const SESSIONS_KIND = "design/sessions/session.kind.mjs";
13
+ export const DECISIONS_KIND = "decisions/decision.kind.mjs";
14
+
15
+ /** A git repository holding a copy of the reference workspace's decisions and design member. */
16
+ export function sessionsRepo(): string {
17
+ const root = scratchDir("chant-sessions-");
18
+ for (const d of ["decisions", "design"]) cpSync(join(REPO, "reference-workspace", d), join(root, d), { recursive: true });
19
+ git(root, "init", "-q");
20
+ commitAll(root, "reference");
21
+ return root;
22
+ }
23
+
24
+ /** A session file's text, sealed when it is closed. */
25
+ export function sessionText(s: { id: string; state: "open" | "closed"; verdicts?: { record: string; principal: string; verdict: string }[]; agenda?: string[] }): string {
26
+ const lines = [
27
+ "---",
28
+ "schema: 1",
29
+ `id: "${s.id}"`,
30
+ `title: "Session ${s.id}"`,
31
+ `state: "${s.state}"`,
32
+ "agenda:",
33
+ ...(s.agenda ?? ["ref-001"]).map((r) => ` - record: "${r}"`),
34
+ "attendance:",
35
+ ' - principal: "lex00"',
36
+ ' class: "person"',
37
+ ' - principal: "alice"',
38
+ ' class: "person"',
39
+ ' - principal: "helper"',
40
+ ' class: "agent"',
41
+ 'opened: "2026-09-24T18:00:00Z"',
42
+ `closed: ${s.state === "closed" ? '"2026-09-24T19:00:00Z"' : "null"}`,
43
+ ...(s.verdicts?.length
44
+ ? ["verdicts:", ...s.verdicts.flatMap((v) => [` - record: "${v.record}"`, ` principal: "${v.principal}"`, ` verdict: "${v.verdict}"`])]
45
+ : ["verdicts: []"]),
46
+ ...(s.state === "closed" ? ['closed_digest: "SEAL"'] : []),
47
+ "---",
48
+ "",
49
+ `# Session ${s.id}`,
50
+ "",
51
+ ];
52
+ const text = lines.join("\n");
53
+ return s.state === "closed" ? text.replace('"SEAL"', `"${sessionSeal(text, "closed_digest")}"`) : text;
54
+ }
55
+
56
+ /** Rewrite ref-001 with the given review entries, each naming `session`. */
57
+ export function reviewed(root: string, reviewers: string[], session: string, state = "decided"): void {
58
+ const file = join(root, "decisions", "ref-001-how-the-app-is-deployed.md");
59
+ const reviews = reviewers.map((r) => ` - reviewer: "${r}"\n verdict: "agree"\n on: "2026-09-24"\n session: "${session}"`).join("\n");
60
+ writeFileSync(
61
+ file,
62
+ readFileSync(file, "utf-8")
63
+ .replace(/^state: .*$/m, `state: "${state}"`)
64
+ .replace(/^reviews: .*$/m, reviewers.length ? `reviews:\n${reviews}` : "reviews: []"),
65
+ );
66
+ }
@@ -13,6 +13,7 @@
13
13
  * | WSP112 | a pinned file that does not exist |
14
14
  * | WSP113 | a pinned file that a superseding record pins at the old hash: the artifact did not follow the decision |
15
15
  * | WSP114 | the records of `--kind` can't be read (fixed) |
16
+ * | WSP115 | a record kind the declaration names is missing or does not load as one (fixed, #2680) |
16
17
  */
17
18
 
18
19
  import type { WorkspaceCheck, WorkspaceDiagnostic } from "../checks";
@@ -80,4 +81,22 @@ export const RECORD_CHECKS: readonly WorkspaceCheck[] = [
80
81
  return [{ checkId: this.id, severity: this.severity, message: `--kind ${facts.kind}: ${facts.error.code}: ${facts.error.message}`, pointer: "" }];
81
82
  },
82
83
  },
84
+ {
85
+ id: "WSP115",
86
+ name: "record-kind-unloadable",
87
+ description: "Every record kind the declaration names is a file that exports a valid recordKind, with the schema it names.",
88
+ severity: "error",
89
+ configurable: false,
90
+ check(ctx) {
91
+ return (ctx.facts?.declaredKinds ?? [])
92
+ .filter((k) => k.reason !== null)
93
+ .map((k) => ({
94
+ checkId: this.id,
95
+ severity: this.severity,
96
+ message: `${k.declared.member === null ? "the workspace" : `member ${k.declared.member}`} declares the record kind ${k.declared.kind}, which can't be loaded: ${k.reason!.code}: ${k.reason!.message}`,
97
+ ...(k.declared.member !== null ? { entity: k.declared.member } : {}),
98
+ pointer: `${k.declared.pointer}/kind`,
99
+ }));
100
+ },
101
+ },
83
102
  ];
@@ -58,6 +58,8 @@ describe("the WSP catalog", () => {
58
58
  "link-kind-unknown",
59
59
  // --kind names a record kind, and records that can't be read check nothing (#2549).
60
60
  "records-unreadable",
61
+ // A declared record kind that doesn't load breaks every reader's default (#2680).
62
+ "record-kind-unloadable",
61
63
  ]);
62
64
  });
63
65
 
@@ -27,6 +27,7 @@
27
27
  * | WSP091 to WSP097 | member links (#2539) |
28
28
  * | WSP101 to WSP106 | generated files |
29
29
  * | WSP111 to WSP114 | records read with `--kind` (#2549) |
30
+ * | WSP115 | the record kinds the declaration names (#2680) |
30
31
  */
31
32
 
32
33
  import { realpathSync } from "node:fs";
@@ -44,6 +45,7 @@ import { gatherGeneratedFacts, GENERATED_CHECKS, type GeneratedFileFacts } from
44
45
  import { gatherLedgerFacts, LEDGER_CHECKS, type MemberLedgerFacts } from "./checks/ledgers";
45
46
  import { gatherPipelineFacts, PIPELINE_CHECKS, type MemberPipelineFacts } from "./checks/pipelines";
46
47
  import { RECORD_CHECKS, type RecordFacts } from "./checks/records";
48
+ import { loadDeclaredKinds, type DeclaredKind } from "./declared-kinds";
47
49
 
48
50
  /**
49
51
  * What the checks beyond the declaration read, gathered from the checkout
@@ -59,6 +61,8 @@ export interface WorkspaceFacts {
59
61
  generated?: readonly GeneratedFileFacts[];
60
62
  /** The records of the kind named with `--kind`, with their pins checked (#2549). */
61
63
  records?: RecordFacts;
64
+ /** The record kinds the declaration names, each loaded, or only looked for under `--at` (#2680). */
65
+ declaredKinds?: readonly DeclaredKind[];
62
66
  }
63
67
 
64
68
  /** What every declaration check reads. */
@@ -444,7 +448,9 @@ export async function runDeclarationChecks(
444
448
  }
445
449
  const { registry, problems } = loadKindRegistry(declaration.pins, root);
446
450
  const gathered = options.gather === false || options.tree ? {} : await gatherWorkspaceFacts(root, declaration, options);
447
- const facts: WorkspaceFacts = options.records ? { ...gathered, records: options.records } : gathered;
451
+ // The declared record kinds load from the working tree; under --at only whether each exists at the revision is checked (#2680).
452
+ const declaredKinds = options.gather === false ? undefined : await loadDeclaredKinds(declaration, tree, root, { load: !options.tree });
453
+ const facts: WorkspaceFacts = { ...gathered, ...(options.records ? { records: options.records } : {}), ...(declaredKinds ? { declaredKinds } : {}) };
448
454
  const ctx: WorkspaceCheckContext = { declaration, tree, groups, kinds: registry, kindProblems: problems, facts };
449
455
  const findings = runWorkspaceChecks(ctx);
450
456
  const { active, suppressed } = applyCheckSettings(declaration, findings);
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://intentius.io/chant/schemas/workspace/composites/v1/composites.schema.json",
4
4
  "title": "chant workspace graph --composites output",
5
- "description": "What `chant workspace graph --composites` prints: each composite instance the workspace's members declare, joined to the components whose contract can deploy it (#2662). Each member of kind chant is read through its own `chant graph --format ir` and `chant graph --components --format ir`, under its own toolchain. An instance with no component is listed with an empty `components` array. chant supplies the rows and never picks one: each match says how it was made and how it crosses members. Readers ignore fields they do not know; a field is only ever added within a version. The reason and error codes are closed lists. Contract version 1 is written by chant 0.81.0 and newer, and this document was added to it by #2662; a chant without --composites refuses the flag. Every code is in the one closed list of `reason-codes.ts`.",
5
+ "description": "What `chant workspace graph --composites` prints: each composite instance the workspace's members declare, joined to the components whose contract can deploy it (#2662). Each member of kind chant is read through its own `chant graph --format ir` and `chant graph --components --format ir`, under its own toolchain. An instance with no component is listed with an empty `components` array. chant supplies the rows and never picks one: each match says how it was made and how it crosses members. Readers ignore fields they do not know; a field is only ever added within a version. The reason and error codes are closed lists. Contract version 1 is written by chant 0.81.0 and newer, and this document was added to it by #2662; a chant without --composites refuses the flag. Each component lists the runtimes it can deploy on, read from its member's chant.config.ts (#2674). Every code is in the one closed list of `reason-codes.ts`.",
6
6
  "oneOf": [
7
7
  {
8
8
  "$ref": "#/$defs/result"
@@ -130,7 +130,8 @@
130
130
  "kind",
131
131
  "status",
132
132
  "reason",
133
- "chant"
133
+ "chant",
134
+ "runtimeReasons"
134
135
  ],
135
136
  "properties": {
136
137
  "name": {
@@ -187,6 +188,28 @@
187
188
  "null"
188
189
  ],
189
190
  "description": "The chant version the member's toolchain reported, or null when a chant older than member-run read it."
191
+ },
192
+ "runtimeReasons": {
193
+ "description": "Why the runtimes of the member's components leave something out: its chant.config.ts, or a lexicon it lists, could not be read. Empty for a member not of kind chant, and when nothing was left out.",
194
+ "type": "array",
195
+ "items": {
196
+ "type": "object",
197
+ "required": [
198
+ "code",
199
+ "message"
200
+ ],
201
+ "properties": {
202
+ "code": {
203
+ "enum": [
204
+ "runtimes-config-unreadable",
205
+ "runtimes-lexicon-unreadable"
206
+ ]
207
+ },
208
+ "message": {
209
+ "type": "string"
210
+ }
211
+ }
212
+ }
190
213
  }
191
214
  },
192
215
  "if": {
@@ -327,7 +350,8 @@
327
350
  "member",
328
351
  "archetype",
329
352
  "composites",
330
- "file"
353
+ "file",
354
+ "runtimes"
331
355
  ],
332
356
  "properties": {
333
357
  "id": {
@@ -368,6 +392,14 @@
368
392
  "null"
369
393
  ],
370
394
  "description": "The component's file from the workspace root, when the member's chant names it."
395
+ },
396
+ "runtimes": {
397
+ "description": "The runtimes the component can deploy on, the list chant run --components --on accepts in its member: local first, then each lexicon the member's chant.config.ts lists whose opRuntime hosts component runs, in config order.",
398
+ "type": "array",
399
+ "minItems": 1,
400
+ "items": {
401
+ "$ref": "#/$defs/runtime"
402
+ }
371
403
  }
372
404
  }
373
405
  },
@@ -466,6 +498,36 @@
466
498
  },
467
499
  "composites": false
468
500
  }
501
+ },
502
+ "runtime": {
503
+ "type": "object",
504
+ "required": [
505
+ "name",
506
+ "lexicon",
507
+ "default",
508
+ "command"
509
+ ],
510
+ "properties": {
511
+ "name": {
512
+ "type": "string",
513
+ "description": "What --on takes: local, or the lexicon's name."
514
+ },
515
+ "lexicon": {
516
+ "type": [
517
+ "string",
518
+ "null"
519
+ ],
520
+ "description": "The lexicon that hosts the runtime, or null for local."
521
+ },
522
+ "default": {
523
+ "type": "boolean",
524
+ "description": "True for the runtime chant run --components uses without --on. That is local until chant.config.ts can name a default."
525
+ },
526
+ "command": {
527
+ "type": "string",
528
+ "description": "The command that deploys the component on this runtime, run in the member's directory: chant run --components <name>, with --on <runtime> for any runtime but local."
529
+ }
530
+ }
469
531
  }
470
532
  }
471
533
  }