@intentius/chant 0.53.0 → 0.54.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 (75) hide show
  1. package/dist/audit/catalog.d.ts +4 -0
  2. package/dist/audit/catalog.d.ts.map +1 -1
  3. package/dist/audit/nginx.d.ts +53 -0
  4. package/dist/audit/nginx.d.ts.map +1 -0
  5. package/dist/audit/rules-doc.d.ts.map +1 -1
  6. package/dist/cli/commands/audit.d.ts.map +1 -1
  7. package/dist/cli/commands/carve-apply.d.ts.map +1 -1
  8. package/dist/cli/commands/carve-status.d.ts +56 -0
  9. package/dist/cli/commands/carve-status.d.ts.map +1 -0
  10. package/dist/cli/handlers/carve-status.d.ts +9 -0
  11. package/dist/cli/handlers/carve-status.d.ts.map +1 -0
  12. package/dist/cli/handlers/components.d.ts.map +1 -1
  13. package/dist/cli/handlers/op-progress.d.ts +37 -4
  14. package/dist/cli/handlers/op-progress.d.ts.map +1 -1
  15. package/dist/cli/handlers/operator.d.ts +61 -1
  16. package/dist/cli/handlers/operator.d.ts.map +1 -1
  17. package/dist/cli/handlers/run.d.ts.map +1 -1
  18. package/dist/cli/main.d.ts.map +1 -1
  19. package/dist/cli/registry.d.ts +9 -1
  20. package/dist/cli/registry.d.ts.map +1 -1
  21. package/dist/components/auto-release.d.ts +3 -1
  22. package/dist/components/auto-release.d.ts.map +1 -1
  23. package/dist/components/cli-support.d.ts +2 -0
  24. package/dist/components/cli-support.d.ts.map +1 -1
  25. package/dist/graph-ir.d.ts +7 -0
  26. package/dist/graph-ir.d.ts.map +1 -1
  27. package/dist/lexicon.d.ts +8 -0
  28. package/dist/lexicon.d.ts.map +1 -1
  29. package/dist/lifecycle/converge-ledger.d.ts +81 -1
  30. package/dist/lifecycle/converge-ledger.d.ts.map +1 -1
  31. package/dist/lifecycle/gate-ledger.d.ts +43 -1
  32. package/dist/lifecycle/gate-ledger.d.ts.map +1 -1
  33. package/dist/lifecycle/release-ledger.d.ts +55 -1
  34. package/dist/lifecycle/release-ledger.d.ts.map +1 -1
  35. package/dist/op/activity-contract.d.ts +15 -1
  36. package/dist/op/activity-contract.d.ts.map +1 -1
  37. package/dist/terraform/manifest.d.ts +31 -6
  38. package/dist/terraform/manifest.d.ts.map +1 -1
  39. package/package.json +1 -1
  40. package/src/audit/catalog.ts +35 -0
  41. package/src/audit/edge-init-safety.test.ts +36 -3
  42. package/src/audit/nginx.test.ts +180 -0
  43. package/src/audit/nginx.ts +379 -0
  44. package/src/audit/rules-doc.ts +1 -0
  45. package/src/cli/commands/audit.ts +8 -3
  46. package/src/cli/commands/carve-apply.test.ts +4 -1
  47. package/src/cli/commands/carve-apply.ts +4 -2
  48. package/src/cli/commands/carve-bridge.test.ts +2 -1
  49. package/src/cli/commands/carve-emit-state.test.ts +3 -1
  50. package/src/cli/commands/carve-status.test.ts +116 -0
  51. package/src/cli/commands/carve-status.ts +163 -0
  52. package/src/cli/handlers/carve-status.ts +30 -0
  53. package/src/cli/handlers/components.ts +5 -1
  54. package/src/cli/handlers/graph.ts +3 -0
  55. package/src/cli/handlers/op-progress.test.ts +67 -1
  56. package/src/cli/handlers/op-progress.ts +70 -14
  57. package/src/cli/handlers/operator.test.ts +302 -1
  58. package/src/cli/handlers/operator.ts +193 -5
  59. package/src/cli/handlers/run.ts +15 -14
  60. package/src/cli/main.test.ts +19 -0
  61. package/src/cli/main.ts +34 -5
  62. package/src/cli/registry.ts +9 -1
  63. package/src/components/auto-release.ts +4 -1
  64. package/src/components/cli-support.ts +4 -2
  65. package/src/graph-ir.ts +7 -0
  66. package/src/lexicon.ts +8 -0
  67. package/src/lifecycle/converge-ledger.test.ts +110 -0
  68. package/src/lifecycle/converge-ledger.ts +110 -2
  69. package/src/lifecycle/gate-ledger.test.ts +81 -1
  70. package/src/lifecycle/gate-ledger.ts +64 -1
  71. package/src/lifecycle/release-ledger.test.ts +64 -0
  72. package/src/lifecycle/release-ledger.ts +79 -1
  73. package/src/op/activity-contract.ts +14 -0
  74. package/src/terraform/manifest.test.ts +52 -0
  75. package/src/terraform/manifest.ts +72 -10
@@ -1,3 +1,5 @@
1
+ import type { ComponentStatusRow } from "./status.js";
2
+ export declare function sanitizeLedgerText(raw: string, maxLen?: number): string;
1
3
  /** One rule's outcome within a tick. */
2
4
  export interface ConvergeRuleOutcome {
3
5
  ruleId: string;
@@ -15,13 +17,70 @@ export interface ConvergeRuleOutcome {
15
17
  op?: string;
16
18
  /** The gate's signal name, for `action: "gated"`. */
17
19
  gateName?: string;
20
+ /**
21
+ * Where this gate's approval happens (#2028), for `action: "gated"` — the
22
+ * PR carrying the change in a gate-as-PR flow, or whatever review surface
23
+ * the dispatching environment knows about.
24
+ *
25
+ * The pending fact is the one a human has to act on, and it used to carry
26
+ * no link at all: `chant operator status`'s pending row was
27
+ * `{rule, op, gate}` and the only affordance it could print was a shell
28
+ * command. Absent when there genuinely is no address — a local tick with no
29
+ * PR behind it — never a synthesized one. See
30
+ * `./gate-ledger.ts`'s `resolveApprovalUrl` for where it comes from, and
31
+ * `GateResolutionRecord.url` for the resolved counterpart.
32
+ */
33
+ url?: string;
18
34
  /** The report reason, for `action: "reported"` (including a flap-damped rule's forced report) — and the human-readable explanation for `action: "gated"`. */
19
35
  reason?: string;
20
36
  }
37
+ /**
38
+ * One component's verdict as the tick observed it (#2027) — the per-entity
39
+ * layer behind the tick's single aggregate `status`.
40
+ *
41
+ * A deliberate subset of `ComponentStatusRow`: the five verdict-bearing
42
+ * fields, joined on the same `component` key `chant components status --live
43
+ * --json` emits, and none of the heavy ones. `recorded`, `build` and
44
+ * `componentBom` are dropped on purpose — they are release/build-ledger
45
+ * content, already readable by that same key, and a tick record is one line
46
+ * of JSON appended every tick forever. `detail` goes through
47
+ * {@link sanitizeLedgerText} for the same reason.
48
+ */
49
+ export interface ConvergeComponentVerdict {
50
+ component: string;
51
+ reconciliation: ComponentStatusRow["reconciliation"];
52
+ /** Human-readable detail backing the verdict, capped to one line. */
53
+ detail: string;
54
+ /** "Observed live", when live evidence was gathered. Absent means "did not look" or "could not look" — see `unobserved`. */
55
+ live?: boolean;
56
+ /** Why live state could not be read for this component (#1089). Mutually exclusive with `live`; this is the row that tripped an aggregate `status: "unknown"`. */
57
+ unobserved?: {
58
+ reason: string;
59
+ detail?: string;
60
+ };
61
+ }
62
+ /**
63
+ * Project the tick's `ComponentStatusRow[]` down to what the ledger keeps.
64
+ * Pure; row order is preserved, so a consumer sees components in the same
65
+ * order the status join produced them.
66
+ */
67
+ export declare function componentVerdicts(rows: readonly ComponentStatusRow[]): ConvergeComponentVerdict[];
21
68
  /** One immutable converge-tick record. */
22
69
  export interface ConvergeTickRecord {
23
70
  /** Schema version, so an incompatible future shape is detected before being misread. */
24
71
  version: 1;
72
+ /**
73
+ * Stable tick id (#2027) — the one thing an outcome, a gate fact or a
74
+ * remediation can point at. Before this, a tick's identity was the
75
+ * `(op, env, timestamp)` triple, so two ticks landing in the same ISO
76
+ * second were indistinguishable and nothing could reference one.
77
+ *
78
+ * Minted by {@link appendConvergeRecord} with `randomUUID()` (the same
79
+ * mint `./lease.ts` uses for a lease token) when the caller doesn't supply
80
+ * one. Optional on the type because `version: 1` records written before
81
+ * #2027 have none — a reader must handle its absence, not assume it.
82
+ */
83
+ id?: string;
25
84
  /** The ConvergeOp's name (`OpConfig.name`). */
26
85
  op: string;
27
86
  env: string;
@@ -31,6 +90,18 @@ export interface ConvergeTickRecord {
31
90
  firedRuleIds: string[];
32
91
  /** Per-rule outcome, for every fired rule. */
33
92
  outcomes: ConvergeRuleOutcome[];
93
+ /**
94
+ * The per-component verdicts this tick observed (#2027), behind the
95
+ * aggregate counts in `summary`.
96
+ *
97
+ * The tick derives these to compute `summary.drifted` and the whole-tick
98
+ * `status`, and used to throw them away — so a reader could say "this
99
+ * environment drifted twice this hour" but could not colour the node that
100
+ * drifted, or name the single unobserved component whose `unknown` verdict
101
+ * refused remediation for everything else. Optional because `version: 1`
102
+ * records written before #2027 have none; absent is not "no components".
103
+ */
104
+ components?: ConvergeComponentVerdict[];
34
105
  /** Aggregate counts backing the tick's one log line. */
35
106
  summary: {
36
107
  drifted: number;
@@ -52,6 +123,13 @@ export type ConvergeTickRecordInput = Omit<ConvergeTickRecord, "version">;
52
123
  * `pushLifecycle` (./git.ts) afterward, same two-step shape every other
53
124
  * ledger write here uses.
54
125
  *
126
+ * Mints `record.id` (#2027) when the input doesn't carry one, so every tick
127
+ * written from here on is referenceable. Unlike `timestamp`, which stays
128
+ * caller-supplied because library code never calls `Date.now()` internally,
129
+ * an id has nothing for a caller to decide — the mint lives here so no
130
+ * writer can forget it. An explicit `input.id` still wins, which is what a
131
+ * test asserting an exact record uses.
132
+ *
55
133
  * Retries the whole read-modify-write cycle (#1485) on `RefCASConflictError`
56
134
  * — `writeBlobToPath`'s ref write is CAS-guarded, so a concurrent writer to
57
135
  * a *different* env's file on the same orphan branch (two operators ticking
@@ -71,7 +149,9 @@ export declare function appendConvergeRecord(input: ConvergeTickRecordInput, opt
71
149
  cwd?: string;
72
150
  }): Promise<{
73
151
  commit: string;
74
- record: ConvergeTickRecord;
152
+ record: ConvergeTickRecord & {
153
+ id: string;
154
+ };
75
155
  }>;
76
156
  /** Read every tick record for `environment`, oldest first. Malformed lines are skipped, not thrown on — a corrupted or hand-edited ledger degrades gracefully, the same stance `readReleaseLedger` takes. */
77
157
  export declare function readConvergeLedger(environment: string, opts?: {
@@ -1 +1 @@
1
- {"version":3,"file":"converge-ledger.d.ts","sourceRoot":"","sources":["../../src/lifecycle/converge-ledger.ts"],"names":[],"mappings":"AAmCA,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;OAQG;IACH,MAAM,EAAE,KAAK,GAAG,UAAU,GAAG,gBAAgB,GAAG,cAAc,GAAG,OAAO,CAAC;IACzE,gEAAgE;IAChE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6JAA6J;IAC7J,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IACjC,wFAAwF;IACxF,OAAO,EAAE,CAAC,CAAC;IACX,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,6IAA6I;IAC7I,SAAS,EAAE,MAAM,CAAC;IAClB,oHAAoH;IACpH,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC,wDAAwD;IACxD,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,uNAAuN;QACvN,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,iHAAiH;IACjH,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,uBAAuB,EAC9B,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACtB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAE,CAAC,CAqBzD;AAED,6MAA6M;AAC7M,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACtB,OAAO,CAAC;IAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CA0B/D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAO1F"}
1
+ {"version":3,"file":"converge-ledger.d.ts","sourceRoot":"","sources":["../../src/lifecycle/converge-ledger.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,UAAU,CAAC;AAcnD,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,SAAsB,GAAG,MAAM,CAKpF;AAcD,wCAAwC;AACxC,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf;;;;;;;;OAQG;IACH,MAAM,EAAE,KAAK,GAAG,UAAU,GAAG,gBAAgB,GAAG,cAAc,GAAG,OAAO,CAAC;IACzE,gEAAgE;IAChE,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,qDAAqD;IACrD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;;;;;;;;;OAYG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,6JAA6J;IAC7J,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,wBAAwB;IACvC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,kBAAkB,CAAC,gBAAgB,CAAC,CAAC;IACrD,qEAAqE;IACrE,MAAM,EAAE,MAAM,CAAC;IACf,4HAA4H;IAC5H,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,kKAAkK;IAClK,UAAU,CAAC,EAAE;QAAE,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAClD;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,SAAS,kBAAkB,EAAE,GAAG,wBAAwB,EAAE,CAejG;AAED,0CAA0C;AAC1C,MAAM,WAAW,kBAAkB;IACjC,wFAAwF;IACxF,OAAO,EAAE,CAAC,CAAC;IACX;;;;;;;;;;OAUG;IACH,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,+CAA+C;IAC/C,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,6IAA6I;IAC7I,SAAS,EAAE,MAAM,CAAC;IAClB,oHAAoH;IACpH,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,EAAE,mBAAmB,EAAE,CAAC;IAChC;;;;;;;;;;OAUG;IACH,UAAU,CAAC,EAAE,wBAAwB,EAAE,CAAC;IACxC,wDAAwD;IACxD,OAAO,EAAE;QACP,OAAO,EAAE,MAAM,CAAC;QAChB,UAAU,EAAE,MAAM,CAAC;QACnB,QAAQ,EAAE,MAAM,CAAC;QACjB,aAAa,EAAE,MAAM,CAAC;QACtB,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;QACnB,OAAO,EAAE,MAAM,CAAC;QAChB,uNAAuN;QACvN,KAAK,CAAC,EAAE,MAAM,CAAC;KAChB,CAAC;IACF,iHAAiH;IACjH,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,MAAM,uBAAuB,GAAG,IAAI,CAAC,kBAAkB,EAAE,SAAS,CAAC,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,uBAAuB,EAC9B,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACtB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,kBAAkB,GAAG;QAAE,EAAE,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAC,CAqB1E;AAED,6MAA6M;AAC7M,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACtB,OAAO,CAAC;IAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CA0B/D;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,kBAAkB,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAO1F"}
@@ -1,3 +1,34 @@
1
+ /**
2
+ * The address of the approval surface for a gate, resolved from the CI
3
+ * environment (#2028).
4
+ *
5
+ * #1485's argument for gate-as-fact was that approval gets an address:
6
+ * "gate-as-PR gives approval a URL, a review surface, and CODEOWNERS as the
7
+ * authorization model." What shipped recorded the gate and not the address,
8
+ * so a pending-approval card had nothing to link to and gate-as-PR stayed a
9
+ * convention. This is the narrow, honest half of that: when a tick (or a
10
+ * `chant approve`) runs inside the PR/MR job that carries the change, the
11
+ * loop genuinely knows where approval happens, and says so. Anywhere else it
12
+ * returns `undefined` and the field is simply absent — never a guess, never a
13
+ * synthesized link.
14
+ *
15
+ * The env-var fallback chain is the same one `--actor` and `--run-id` already
16
+ * use (`../cli/handlers/components.ts`): GitHub Actions first, then GitLab CI.
17
+ *
18
+ * - GitHub Actions on a `pull_request` event: `GITHUB_SERVER_URL` +
19
+ * `GITHUB_REPOSITORY` + the PR number, which `GITHUB_REF_NAME` carries as
20
+ * `<n>/merge`. A push-event run has no PR, so it resolves to nothing.
21
+ * - GitLab CI on a merge-request pipeline: `CI_MERGE_REQUEST_PROJECT_URL` +
22
+ * `CI_MERGE_REQUEST_IID`.
23
+ */
24
+ export declare function resolveApprovalUrl(env?: NodeJS.ProcessEnv): string | undefined;
25
+ /**
26
+ * Whether `raw` is an address worth recording as one: an absolute `http`/
27
+ * `https` URL. A gate's address is a link a reader is expected to follow, so
28
+ * a relative path or a `file:`/`javascript:` scheme is refused at the CLI
29
+ * boundary rather than written into an immutable record.
30
+ */
31
+ export declare function isApprovalUrl(raw: string): boolean;
1
32
  /** One immutable gate-resolution record. */
2
33
  export interface GateResolutionRecord {
3
34
  /** Schema version, so an incompatible future shape is detected before being misread. */
@@ -10,8 +41,19 @@ export interface GateResolutionRecord {
10
41
  resolvedBy: string;
11
42
  /** ISO-8601 timestamp, caller-supplied (library code never calls `Date.now()` internally). */
12
43
  timestamp: string;
13
- /** Optional free-text context (e.g. a PR URL — "or a merged PR" is the issue's other resolution path; recording its link here keeps both paths visible from one ledger). */
44
+ /** Optional free-text context. Before #2028 this was also where a PR link went by convention; put the link in {@link GateResolutionRecord.url} instead and leave this for prose. */
14
45
  note?: string;
46
+ /**
47
+ * The address this resolution happened at (#2028) — the PR that carried the
48
+ * change, the review thread, whatever the approval surface was. Typed, so
49
+ * "resolved by this PR" is machine-readable instead of a reader sniffing
50
+ * `note` for something that looks like a link.
51
+ *
52
+ * `chant approve --url` sets it; absent when the resolver genuinely had no
53
+ * address (a human at a terminal, no PR). Always an absolute `http`/`https`
54
+ * URL — see {@link isApprovalUrl}.
55
+ */
56
+ url?: string;
15
57
  }
16
58
  export type GateResolutionInput = Omit<GateResolutionRecord, "version">;
17
59
  /** Append one immutable gate-resolution record. Does not push to the remote — call `pushLifecycle` (./git.ts) afterward, same two-step shape every other ledger write here uses. Retries on `RefCASConflictError` the same way `appendConvergeRecord` does (./converge-ledger.ts) — a concurrent writer to a different op's/env's file on the same orphan branch is the ordinary case, not an edge case. The baseline read must be `readPathSha` + `readBlobBySha` rather than `readBlobFromPath`, so the exact sha `existing` came from can be passed as `expectPriorPathSha` — see `writeBlobToPath` (./git.ts) for the race that closes. */
@@ -1 +1 @@
1
- {"version":3,"file":"gate-ledger.d.ts","sourceRoot":"","sources":["../../src/lifecycle/gate-ledger.ts"],"names":[],"mappings":"AA4CA,4CAA4C;AAC5C,MAAM,WAAW,oBAAoB;IACnC,wFAAwF;IACxF,OAAO,EAAE,CAAC,CAAC;IACX,6CAA6C;IAC7C,EAAE,EAAE,MAAM,CAAC;IACX,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,yHAAyH;IACzH,UAAU,EAAE,MAAM,CAAC;IACnB,8FAA8F;IAC9F,SAAS,EAAE,MAAM,CAAC;IAClB,4KAA4K;IAC5K,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,CAAC;AAMxE,+mBAA+mB;AAC/mB,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,mBAAmB,EAC1B,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACtB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAAC,CAqB3D;AAED,oPAAoP;AACpP,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,MAAM,EACV,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACtB,OAAO,CAAC;IAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CA0BjE;AAED,uOAAuO;AACvO,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,oBAAoB,EAAE,EAC/B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,oBAAoB,GAAG,SAAS,CASlC"}
1
+ {"version":3,"file":"gate-ledger.d.ts","sourceRoot":"","sources":["../../src/lifecycle/gate-ledger.ts"],"names":[],"mappings":"AA4CA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,GAAE,MAAM,CAAC,UAAwB,GAAG,MAAM,GAAG,SAAS,CAY3F;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAOlD;AAED,4CAA4C;AAC5C,MAAM,WAAW,oBAAoB;IACnC,wFAAwF;IACxF,OAAO,EAAE,CAAC,CAAC;IACX,6CAA6C;IAC7C,EAAE,EAAE,MAAM,CAAC;IACX,uEAAuE;IACvE,IAAI,EAAE,MAAM,CAAC;IACb,yHAAyH;IACzH,UAAU,EAAE,MAAM,CAAC;IACnB,8FAA8F;IAC9F,SAAS,EAAE,MAAM,CAAC;IAClB,oLAAoL;IACpL,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;;;;;OASG;IACH,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,mBAAmB,GAAG,IAAI,CAAC,oBAAoB,EAAE,SAAS,CAAC,CAAC;AAMxE,+mBAA+mB;AAC/mB,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,mBAAmB,EAC1B,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACtB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,oBAAoB,CAAA;CAAE,CAAC,CAqB3D;AAED,oPAAoP;AACpP,wBAAsB,mBAAmB,CACvC,EAAE,EAAE,MAAM,EACV,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACtB,OAAO,CAAC;IAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CA0BjE;AAED,uOAAuO;AACvO,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,oBAAoB,EAAE,EAC/B,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,GACf,oBAAoB,GAAG,SAAS,CASlC"}
@@ -27,6 +27,24 @@
27
27
  * straightforward digest comparison over this ledger — never a guess through
28
28
  * CI logs.
29
29
  */
30
+ /**
31
+ * The id space a `ReleaseRecord.runId` lives in, and how to reach the run
32
+ * (#2045).
33
+ */
34
+ export interface RunOrigin {
35
+ /**
36
+ * Which id space minted `runId`: `github` (a GitHub Actions run id — also
37
+ * the spelling Forgejo Actions uses, since it implements the same env
38
+ * contract; `url` disambiguates the actual host), `gitlab` (a CI pipeline
39
+ * id), `op` (an orchestrator/Op run id), or `local` (minted on the machine
40
+ * that ran the deploy, resolvable nowhere).
41
+ */
42
+ forge: "github" | "gitlab" | "op" | "local";
43
+ /** Repo/project slug on the forge (`GITHUB_REPOSITORY` / `CI_PROJECT_PATH`), when the environment named one. */
44
+ repo?: string;
45
+ /** Resolved link to the run, when the environment provided enough to build one. */
46
+ url?: string;
47
+ }
30
48
  /**
31
49
  * One immutable deploy record: `(component, env, artifact digest, git sha,
32
50
  * run id, timestamp, actor)`, referencing the build archive by digest — the
@@ -50,8 +68,19 @@ export interface ReleaseRecord {
50
68
  digest: string;
51
69
  /** Git commit SHA the deploy was built from. */
52
70
  gitSha: string;
53
- /** Orchestrator run identifier (a `DriverRunResult`/Op run id, or a CI run id in generate mode) — the pointer back into the deploy event log. */
71
+ /** Orchestrator run identifier (a `DriverRunResult`/Op run id, or a CI run id in generate mode) — the pointer back into the deploy event log. Which id space it lives in, and how to follow it, is `runOrigin`'s job (#2045). */
54
72
  runId: string;
73
+ /**
74
+ * Where `runId` can be resolved (#2045). A bare `runId` names a run in one
75
+ * of several mutually incompatible id spaces — a GitHub Actions run id, a
76
+ * GitLab pipeline id, an Op run id, a locally minted id — and without this
77
+ * a reader can tell them apart only by inference from whoever wrote the
78
+ * record. Written at record time from the same environment the id came
79
+ * from, so the two cannot skew. Optional: records written before this
80
+ * field existed, and callers passing an id whose origin they did not
81
+ * declare, simply have none.
82
+ */
83
+ runOrigin?: RunOrigin;
55
84
  /**
56
85
  * When this record was written. The caller supplies this — the session
57
86
  * cannot call `Date.now()` reliably in every context, so the CLI resolves
@@ -104,6 +133,31 @@ export declare class InvalidReleaseRecordError extends Error {
104
133
  export declare function validateReleaseRecord(record: Partial<ReleaseRecord>): string[];
105
134
  /** Input a caller supplies to record one deploy — `version` is filled in, everything else is required (see `ReleaseRecord`). */
106
135
  export type ReleaseRecordInput = Omit<ReleaseRecord, "version">;
136
+ /**
137
+ * Resolve a run id and its origin from the same environment, in one step, so
138
+ * the two cannot skew (#2045). Resolution order mirrors what
139
+ * `chant components release` always did — explicit id, then
140
+ * `GITHUB_RUN_ID`, then `CI_PIPELINE_ID`, then a locally minted
141
+ * `local-<ms>` — but now each source also names its id space:
142
+ *
143
+ * - the GitHub path records `repo` (`GITHUB_REPOSITORY`) and builds the run
144
+ * `url` from `GITHUB_SERVER_URL` — which also makes a Forgejo instance's
145
+ * runs resolvable, since Forgejo Actions implements the same env contract;
146
+ * - the GitLab path records `repo` (`CI_PROJECT_PATH`) and takes
147
+ * `CI_PIPELINE_URL` verbatim;
148
+ * - the local mint says `local` in a field instead of only in the id's
149
+ * spelling.
150
+ *
151
+ * An explicit id still gets an origin when it matches the id the environment
152
+ * itself is advertising (a CI job passing `--run-id $GITHUB_RUN_ID`);
153
+ * otherwise the caller's id is recorded without one — guessing an origin the
154
+ * caller did not state would be exactly the inference this field exists to
155
+ * remove.
156
+ */
157
+ export declare function resolveRunId(explicit?: string, env?: NodeJS.ProcessEnv): {
158
+ runId: string;
159
+ runOrigin?: RunOrigin;
160
+ };
107
161
  /**
108
162
  * Append one immutable release record to the `<env>` ledger on the
109
163
  * `chant/lifecycle` orphan branch. Throws `InvalidReleaseRecordError` before
@@ -1 +1 @@
1
- {"version":3,"file":"release-ledger.d.ts","sourceRoot":"","sources":["../../src/lifecycle/release-ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,4FAA4F;IAC5F,OAAO,EAAE,CAAC,CAAC;IACX,6FAA6F;IAC7F,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,iJAAiJ;IACjJ,KAAK,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gOAAgO;IAChO,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAaD,4KAA4K;AAC5K,qBAAa,yBAA0B,SAAQ,KAAK;aACtB,OAAO,EAAE,MAAM,EAAE;gBAAjB,OAAO,EAAE,MAAM,EAAE;CAI9C;AAED,yFAAyF;AACzF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,MAAM,EAAE,CAO9E;AAED,gIAAgI;AAChI,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;AAEhE;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,kBAAkB,EACzB,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACtB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,aAAa,CAAA;CAAE,CAAC,CAQpD;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACtB,OAAO,CAAC;IAAE,OAAO,EAAE,aAAa,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAiB1D;AAED,4EAA4E;AAC5E,wBAAsB,uBAAuB,CAAC,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAExF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CASvF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAE1F"}
1
+ {"version":3,"file":"release-ledger.d.ts","sourceRoot":"","sources":["../../src/lifecycle/release-ledger.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAKH;;;GAGG;AACH,MAAM,WAAW,SAAS;IACxB;;;;;;OAMG;IACH,KAAK,EAAE,QAAQ,GAAG,QAAQ,GAAG,IAAI,GAAG,OAAO,CAAC;IAC5C,gHAAgH;IAChH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,mFAAmF;IACnF,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,4FAA4F;IAC5F,OAAO,EAAE,CAAC,CAAC;IACX,6FAA6F;IAC7F,SAAS,EAAE,MAAM,CAAC;IAClB,wCAAwC;IACxC,GAAG,EAAE,MAAM,CAAC;IACZ;;;;;;;OAOG;IACH,MAAM,EAAE,MAAM,CAAC;IACf,gDAAgD;IAChD,MAAM,EAAE,MAAM,CAAC;IACf,iOAAiO;IACjO,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;OASG;IACH,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB;;;;;OAKG;IACH,SAAS,EAAE,MAAM,CAAC;IAClB,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd;;;;;;;;;OASG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gOAAgO;IAChO,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;;;;OAMG;IACH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;;;;;;;OAUG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAaD,4KAA4K;AAC5K,qBAAa,yBAA0B,SAAQ,KAAK;aACtB,OAAO,EAAE,MAAM,EAAE;gBAAjB,OAAO,EAAE,MAAM,EAAE;CAI9C;AAED,yFAAyF;AACzF,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,OAAO,CAAC,aAAa,CAAC,GAAG,MAAM,EAAE,CAO9E;AAED,gIAAgI;AAChI,MAAM,MAAM,kBAAkB,GAAG,IAAI,CAAC,aAAa,EAAE,SAAS,CAAC,CAAC;AAEhE;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,YAAY,CAC1B,QAAQ,CAAC,EAAE,MAAM,EACjB,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,SAAS,CAAA;CAAE,CAsB1C;AAED;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,kBAAkB,EACzB,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACtB,OAAO,CAAC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,aAAa,CAAA;CAAE,CAAC,CAQpD;AAED;;;;;GAKG;AACH,wBAAsB,iBAAiB,CACrC,WAAW,EAAE,MAAM,EACnB,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACtB,OAAO,CAAC;IAAE,OAAO,EAAE,aAAa,EAAE,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,CAAC,CAiB1D;AAED,4EAA4E;AAC5E,wBAAsB,uBAAuB,CAAC,IAAI,CAAC,EAAE;IAAE,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAExF;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,aAAa,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,aAAa,CAAC,CASvF;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,aAAa,EAAE,EAAE,MAAM,EAAE,MAAM,GAAG,aAAa,EAAE,CAE1F"}
@@ -64,9 +64,23 @@ export interface ActivityContract<Args = unknown, Return = unknown> {
64
64
  * per #1290 — a later step's reference into this one's output.
65
65
  */
66
66
  returns?: z.ZodType<Return>;
67
+ /**
68
+ * Names of top-level args whose values identify what the activity's effect
69
+ * touches in the estate (#2022) — an entity id, a resource address, a
70
+ * target endpoint — as opposed to scope (`env`, a stack name) or mechanics
71
+ * (a timeout, a mode). op.json's IR resolves these per step into
72
+ * `OpIRActivityStep.entities`, so a renderer can join a step to the estate
73
+ * nodes it touches instead of inventing joins off scope fields. Optional
74
+ * and partial by design, like the contract itself: an activity whose
75
+ * touched set is not spelled in its args (e.g. an applier that derives
76
+ * stack members internally) simply declares none.
77
+ */
78
+ entities?: string[];
67
79
  }
68
80
  /** Declare an activity contract. */
69
- export declare function activityContract<ArgsSchema extends z.ZodTypeAny, ReturnSchema extends z.ZodTypeAny = never>(name: string, args: ArgsSchema, returns?: ReturnSchema): ActivityContract<z.infer<ArgsSchema>, ReturnSchema extends z.ZodTypeAny ? z.infer<ReturnSchema> : unknown>;
81
+ export declare function activityContract<ArgsSchema extends z.ZodTypeAny, ReturnSchema extends z.ZodTypeAny = never>(name: string, args: ArgsSchema, returns?: ReturnSchema, opts?: {
82
+ entities?: string[];
83
+ }): ActivityContract<z.infer<ArgsSchema>, ReturnSchema extends z.ZodTypeAny ? z.infer<ReturnSchema> : unknown>;
70
84
  /** Structural guard for a value produced by {@link activityContract}. */
71
85
  export declare function isActivityContract(value: unknown): value is ActivityContract;
72
86
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"activity-contract.d.ts","sourceRoot":"","sources":["../../src/op/activity-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,QAAQ,EAAiD,MAAM,SAAS,CAAC;AAEvF,2HAA2H;AAC3H,eAAO,MAAM,uBAAuB,6FAO1B,CAAC;AAEX,QAAA,MAAM,cAAc,eAA0C,CAAC;AAE/D;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB,CAAC,IAAI,GAAG,OAAO,EAAE,MAAM,GAAG,OAAO;IAChE,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC;IAChC,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;CAC7B;AAED,oCAAoC;AACpC,wBAAgB,gBAAgB,CAAC,UAAU,SAAS,CAAC,CAAC,UAAU,EAAE,YAAY,SAAS,CAAC,CAAC,UAAU,GAAG,KAAK,EACzG,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,UAAU,EAChB,OAAO,CAAC,EAAE,YAAY,GACrB,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,YAAY,SAAS,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,CAO5G;AAED,yEAAyE;AACzE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,IAAI,CAIhH;AAID,MAAM,WAAW,qBAAqB;IACpC,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;CACjB;AAmCD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9E;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,UAAU,GAAG,SAAS,CASxG;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEvF,wBAAgB,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,GAAG,mBAAmB,GAAG,SAAS,CAQrF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC,EACvD,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAC/C,qBAAqB,EAAE,CA+CzB"}
1
+ {"version":3,"file":"activity-contract.d.ts","sourceRoot":"","sources":["../../src/op/activity-contract.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmCG;AAEH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,QAAQ,EAAiD,MAAM,SAAS,CAAC;AAEvF,2HAA2H;AAC3H,eAAO,MAAM,uBAAuB,6FAO1B,CAAC;AAEX,QAAA,MAAM,cAAc,eAA0C,CAAC;AAE/D;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,gBAAgB,CAAC,IAAI,GAAG,OAAO,EAAE,MAAM,GAAG,OAAO;IAChE,QAAQ,CAAC,CAAC,cAAc,CAAC,EAAE,IAAI,CAAC;IAChC,iEAAiE;IACjE,IAAI,EAAE,MAAM,CAAC;IACb,gFAAgF;IAChF,IAAI,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB;;;;;OAKG;IACH,OAAO,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAC5B;;;;;;;;;;OAUG;IACH,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,oCAAoC;AACpC,wBAAgB,gBAAgB,CAAC,UAAU,SAAS,CAAC,CAAC,UAAU,EAAE,YAAY,SAAS,CAAC,CAAC,UAAU,GAAG,KAAK,EACzG,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,UAAU,EAChB,OAAO,CAAC,EAAE,YAAY,EACtB,IAAI,CAAC,EAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CAAE,GAC7B,gBAAgB,CAAC,CAAC,CAAC,KAAK,CAAC,UAAU,CAAC,EAAE,YAAY,SAAS,CAAC,CAAC,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,YAAY,CAAC,GAAG,OAAO,CAAC,CAQ5G;AAED,yEAAyE;AACzE,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,gBAAgB,CAE5E;AAED;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAAG,IAAI,CAIhH;AAID,MAAM,WAAW,qBAAqB;IACpC,4CAA4C;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,+CAA+C;IAC/C,KAAK,EAAE,MAAM,CAAC;IACd,kDAAkD;IAClD,EAAE,EAAE,MAAM,CAAC;IACX,kDAAkD;IAClD,OAAO,EAAE,MAAM,CAAC;CACjB;AAmCD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAE9E;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,EAAE,IAAI,EAAE,aAAa,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,UAAU,GAAG,SAAS,CASxG;AAED;;;;;;;;;GASG;AACH,MAAM,MAAM,mBAAmB,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,QAAQ,GAAG,OAAO,CAAC;AAEvF,wBAAgB,eAAe,CAAC,MAAM,EAAE,CAAC,CAAC,UAAU,GAAG,mBAAmB,GAAG,SAAS,CAQrF;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CACnC,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,QAAQ,GAAG,WAAW,CAAC,EACvD,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAC,GAC/C,qBAAqB,EAAE,CA+CzB"}
@@ -10,8 +10,16 @@
10
10
  import type { CarveReport } from "./carve.js";
11
11
  import type { OwnershipMarker } from "../ownership.js";
12
12
  export declare const CARVE_MANIFEST_SUFFIX = ".carve.json";
13
+ /**
14
+ * Version 2 (#2039): `emit.files`, `bridge.written`, `bridge.patch` and
15
+ * `apply.stampedFiles` are recorded relative to the manifest's own directory,
16
+ * so the manifest stays true when the tree is copied, moved, or checked out
17
+ * elsewhere. Version-1 manifests recorded them absolute; readers resolve
18
+ * either through `resolveManifestFilePath`.
19
+ */
20
+ export declare const CARVE_MANIFEST_VERSION = 2;
13
21
  export interface CarveManifest {
14
- version: 1;
22
+ version: 1 | 2;
15
23
  /** Terraform address of the carved resource, e.g. `aws_s3_bucket.assets`. */
16
24
  target: string;
17
25
  /** Terraform resource type, when known. */
@@ -25,7 +33,13 @@ export interface CarveManifest {
25
33
  /** Recorded by `carve emit`. */
26
34
  emit?: {
27
35
  source: "tfstate" | "live";
28
- /** Emitted chant source file path(s). */
36
+ /**
37
+ * Emitted chant source file path(s), relative to the manifest's directory
38
+ * (absolute in version-1 manifests). The manifest sits at the scaffolded
39
+ * project's root, so these are also project-root-relative — the same base
40
+ * `chant graph --format ir` uses for `sourceLoc.file`, which is the join
41
+ * from a carved Terraform address to the chant entity it became (#2040).
42
+ */
29
43
  files: string[];
30
44
  /**
31
45
  * Deferred outbound inputs declared as build parameters in the emitted
@@ -45,9 +59,10 @@ export interface CarveManifest {
45
59
  };
46
60
  /** Recorded by `carve bridge`. */
47
61
  bridge?: {
62
+ /** Files written, relative to the manifest's directory (absolute in version-1 manifests). */
48
63
  written: string[];
49
64
  appliedInPlace: boolean;
50
- /** The git-applyable `.patch` carrying the whole survivor edit. */
65
+ /** The git-applyable `.patch` carrying the whole survivor edit, relative to the manifest's directory (absolute in version-1 manifests). */
51
66
  patch?: string;
52
67
  /** Carved addresses whose own `.tf` block the rewrites remove (#998). */
53
68
  excised?: string[];
@@ -57,7 +72,7 @@ export interface CarveManifest {
57
72
  apply?: {
58
73
  marker: OwnershipMarker;
59
74
  ownershipTags: Record<string, string>;
60
- /** Emitted files the marker was stamped into (`--write-source`). */
75
+ /** Emitted files the marker was stamped into (`--write-source`), relative to the manifest's directory (absolute in version-1 manifests). */
61
76
  stampedFiles?: string[];
62
77
  at: string;
63
78
  };
@@ -66,9 +81,15 @@ export interface CarveManifest {
66
81
  export declare function carveSlug(target: string): string;
67
82
  /** Where the manifest for `target` lives inside an output dir. */
68
83
  export declare function carveManifestPath(outDir: string, target: string): string;
84
+ /**
85
+ * Resolve a manifest-recorded file path against the manifest's directory.
86
+ * Absolute paths (version-1 manifests) pass through untouched, so both
87
+ * on-disk spellings read the same.
88
+ */
89
+ export declare function resolveManifestFilePath(manifestDir: string, p: string): string;
69
90
  /** Write (or overwrite) a manifest into the output dir. Returns the path. */
70
91
  export declare function writeCarveManifest(outDir: string, manifest: CarveManifest): string;
71
- /** Read a manifest, or null when absent/unreadable/not a v1 manifest. */
92
+ /** Read a manifest, or null when absent/unreadable/not a known-version manifest. */
72
93
  export declare function readCarveManifest(path: string): CarveManifest | null;
73
94
  /** All carve manifest paths in an output dir. */
74
95
  export declare function listCarveManifests(outDir: string): string[];
@@ -84,6 +105,10 @@ export interface ResolvedManifest {
84
105
  * several is an error naming what is (or is not) there.
85
106
  */
86
107
  export declare function resolveCarveManifest(outDir: string, select?: string): ResolvedManifest;
87
- /** Read-modify-write a section of an existing manifest. No-op when absent. */
108
+ /**
109
+ * Read-modify-write a section of an existing manifest. No-op when absent.
110
+ * The whole manifest is re-normalized on write, so updating a version-1
111
+ * manifest also migrates its recorded paths to the relative contract.
112
+ */
88
113
  export declare function updateCarveManifest(outDir: string, target: string, patch: Partial<Pick<CarveManifest, "boundary" | "emit" | "bridge" | "apply">>): string | undefined;
89
114
  //# sourceMappingURL=manifest.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/terraform/manifest.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"manifest.d.ts","sourceRoot":"","sources":["../../src/terraform/manifest.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intentius/chant",
3
- "version": "0.53.0",
3
+ "version": "0.54.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",
@@ -127,6 +127,22 @@ export const CF_STATIC_ASSETS: Authority = {
127
127
  name: "Cloudflare Workers — Static assets",
128
128
  url: "https://developers.cloudflare.com/workers/static-assets/",
129
129
  };
130
+ export const MOZILLA_TLS: Authority = {
131
+ name: "Mozilla — Server Side TLS",
132
+ url: "https://wiki.mozilla.org/Security/Server_Side_TLS",
133
+ };
134
+ export const CWE_DIR_LISTING: Authority = {
135
+ name: "CWE-548 — Exposure of Information Through Directory Listing",
136
+ url: "https://cwe.mitre.org/data/definitions/548.html",
137
+ };
138
+ export const GIXY_ALIAS: Authority = {
139
+ name: "Gixy — alias traversal",
140
+ url: "https://github.com/yandex/gixy/blob/master/docs/en/plugins/aliastraversal.md",
141
+ };
142
+ export const NGINX_STUB_STATUS: Authority = {
143
+ name: "nginx — ngx_http_stub_status_module",
144
+ url: "https://nginx.org/en/docs/http/ngx_http_stub_status_module.html",
145
+ };
130
146
  export const CWE_HARDCODED_CREDS: Authority = {
131
147
  name: "CWE-798 — Use of Hard-coded Credentials",
132
148
  url: "https://cwe.mitre.org/data/definitions/798.html",
@@ -197,6 +213,14 @@ export const RULE_CATEGORY: Record<string, Category> = {
197
213
  WRG004: "security",
198
214
  WRG005: "security",
199
215
  WRG006: "security",
216
+ // NGX — nginx config audit (#1979), lexicon-independent like SEC/WRG.
217
+ NGX001: "security",
218
+ NGX002: "security",
219
+ NGX003: "security",
220
+ NGX004: "security",
221
+ NGX005: "security",
222
+ NGX006: "best-practice",
223
+ NGX007: "best-practice",
200
224
  // AGT — agent configuration (`chant audit --agents`). Core-owned like COR/EXT:
201
225
  // these run against the machine's own agent config, not against any one
202
226
  // lexicon's emitted output, so no lexicon ships them.
@@ -247,6 +271,17 @@ export const RULE_CATALOG: Record<string, RuleMeta> = {
247
271
  WRG005: meta("WRG005", M, G, "Non-production environment shares a data store with production", "Give the non-production environment its own KV namespace/R2 bucket/D1 database id instead of reusing production's.", [CF_ENVIRONMENTS]),
248
272
  WRG006: meta("WRG006", M, G, "Static assets served from the project root", "Point [site].bucket / [assets].directory at a dedicated public output folder, not the project root, so non-public files (config, source maps, .git) aren't served.", [CF_STATIC_ASSETS]),
249
273
 
274
+ // nginx config audit (#1979, the #446 follow-up) — lexicon-independent,
275
+ // same shape as SEC/WRG: `nginx.ts` scans every nginx config it detects
276
+ // regardless of which (if any) audit lexicons are installed.
277
+ NGX001: meta("NGX001", M, G, "Deprecated TLS protocol enabled", "Remove SSLv2/SSLv3/TLSv1/TLSv1.1 from ssl_protocols and serve TLSv1.2 and TLSv1.3 only.", [MOZILLA_TLS]),
278
+ NGX002: meta("NGX002", M, G, "Weak cipher suite enabled", "Remove the RC4/DES/MD5/NULL/EXPORT-class entries from ssl_ciphers and use a modern cipher list (e.g. Mozilla's intermediate configuration).", [MOZILLA_TLS]),
279
+ NGX003: meta("NGX003", M, G, "Directory listing enabled", "Remove `autoindex on` (or scope it to a directory that is genuinely meant to be enumerated) so file listings aren't served to anyone who asks.", [CWE_DIR_LISTING]),
280
+ NGX004: meta("NGX004", M, G, "alias path traversal", "End the location prefix with \"/\" so it matches the trailing slash of the alias target — without it, a request for \"<prefix>../\" escapes the aliased directory.", [GIXY_ALIAS]),
281
+ NGX005: meta("NGX005", M, G, "Status endpoint with no access restriction", "Restrict the stub_status location with allow/deny (or auth_basic/auth_request) so connection metrics aren't public reconnaissance.", [NGINX_STUB_STATUS]),
282
+ NGX006: meta("NGX006", R, G, "Server version disclosure", "Add `server_tokens off;` in the http block so nginx stops advertising its exact version in the Server header and error pages."),
283
+ NGX007: meta("NGX007", R, G, "Access logging disabled at server scope", "Re-enable access_log at http/server scope (silencing a single noisy location is fine) so requests are recorded for incident investigation."),
284
+
250
285
  // ── Agent configuration (`chant audit --agents`) ──────────────────
251
286
  AGT001: agentMeta(
252
287
  "AGT001",
@@ -54,8 +54,8 @@ function resolveRelative(from: string, spec: string): string | undefined {
54
54
  return undefined;
55
55
  }
56
56
 
57
- /** Static (non-type-only) imports and re-exports of a file, resolved to paths. */
58
- function staticImports(sf: ts.SourceFile): string[] {
57
+ /** Static (non-type-only) import/re-export specifiers of a file. */
58
+ function staticSpecifiers(sf: ts.SourceFile): string[] {
59
59
  const out: string[] = [];
60
60
  for (const stmt of sf.statements) {
61
61
  let spec: string | undefined;
@@ -66,7 +66,16 @@ function staticImports(sf: ts.SourceFile): string[] {
66
66
  if (stmt.isTypeOnly) continue;
67
67
  spec = (stmt.moduleSpecifier as ts.StringLiteral).text;
68
68
  }
69
- if (!spec || !spec.startsWith(".")) continue;
69
+ if (spec) out.push(spec);
70
+ }
71
+ return out;
72
+ }
73
+
74
+ /** Static (non-type-only) imports and re-exports of a file, resolved to paths. */
75
+ function staticImports(sf: ts.SourceFile): string[] {
76
+ const out: string[] = [];
77
+ for (const spec of staticSpecifiers(sf)) {
78
+ if (!spec.startsWith(".")) continue;
70
79
  const resolved = resolveRelative(sf.fileName, spec);
71
80
  if (resolved) out.push(resolved);
72
81
  }
@@ -171,4 +180,28 @@ describe("edge-imported lexicon modules are init-safe", () => {
171
180
  for (const sf of transitiveClosure(barrels()).values()) offenders.push(...moduleScopeOffenses(sf));
172
181
  expect(offenders, "move these into a function (lazy) — they crash edge bundles").toEqual([]);
173
182
  });
183
+
184
+ test("no static import of the full @intentius/chant barrel in the transitive import graph", () => {
185
+ // The relative-import walk above stops at package specifiers, and the bare
186
+ // core barrel is exactly the package import that must never appear: its
187
+ // static graph reaches build() → discovery/fold-import → the TypeScript
188
+ // compiler, whose own module init touches __filename and crashes workerd
189
+ // at startup. This is how WHM503's `../../render` import (whose render.ts
190
+ // imports `@intentius/chant` for Composite) broke the hosted audit's
191
+ // 0.53.1 bump. Subpath imports (`@intentius/chant/lint/post-synth`,
192
+ // `@intentius/chant/effect-receipt`, …) are the deliberately edge-clean
193
+ // seams and stay allowed.
194
+ const offenders: string[] = [];
195
+ for (const sf of transitiveClosure(barrels()).values()) {
196
+ for (const spec of staticSpecifiers(sf)) {
197
+ if (spec === "@intentius/chant") {
198
+ offenders.push(sf.fileName.replace(lexiconsDir, "lexicons"));
199
+ }
200
+ }
201
+ }
202
+ expect(
203
+ offenders,
204
+ "these edge-reachable modules import the full core barrel — import the specific subpath (or split the needed piece into a light module) instead",
205
+ ).toEqual([]);
206
+ });
174
207
  });