dorfl 0.8.0 → 0.10.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/cli.d.ts.map +1 -1
- package/dist/cli.js +63 -25
- package/dist/cli.js.map +1 -1
- package/dist/config.d.ts +82 -7
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +14 -2
- package/dist/config.js.map +1 -1
- package/dist/do.d.ts +16 -1
- package/dist/do.d.ts.map +1 -1
- package/dist/do.js +14 -9
- package/dist/do.js.map +1 -1
- package/dist/env-config.d.ts.map +1 -1
- package/dist/env-config.js +25 -3
- package/dist/env-config.js.map +1 -1
- package/dist/intake-trigger-template.d.ts +91 -53
- package/dist/intake-trigger-template.d.ts.map +1 -1
- package/dist/intake-trigger-template.js +213 -114
- package/dist/intake-trigger-template.js.map +1 -1
- package/dist/intake.d.ts +53 -4
- package/dist/intake.d.ts.map +1 -1
- package/dist/intake.js +59 -15
- package/dist/intake.js.map +1 -1
- package/dist/placement.d.ts +42 -33
- package/dist/placement.d.ts.map +1 -1
- package/dist/placement.js +20 -17
- package/dist/placement.js.map +1 -1
- package/dist/repo-config.d.ts +1 -1
- package/dist/repo-config.d.ts.map +1 -1
- package/dist/repo-config.js +35 -0
- package/dist/repo-config.js.map +1 -1
- package/dist/tasking.d.ts +46 -1
- package/dist/tasking.d.ts.map +1 -1
- package/dist/tasking.js +48 -21
- package/dist/tasking.js.map +1 -1
- package/package.json +1 -1
- package/src/cli.ts +75 -28
- package/src/config.ts +96 -9
- package/src/do.ts +30 -10
- package/src/env-config.ts +25 -3
- package/src/intake-trigger-template.ts +263 -143
- package/src/intake.ts +136 -18
- package/src/placement.ts +42 -36
- package/src/repo-config.ts +35 -0
- package/src/tasking.ts +75 -43
|
@@ -6,9 +6,11 @@
|
|
|
6
6
|
* STRUCTURALLY VALIDATES it, mirroring the snapshot-assertion style of
|
|
7
7
|
* `advance-lifecycle-template.ts` / `advance-ci-template.ts` (the package depends on
|
|
8
8
|
* NO YAML lib, so the checks are presence/shape assertions over the raw text). It
|
|
9
|
-
* ALSO carries the PURE
|
|
10
|
-
*
|
|
11
|
-
*
|
|
9
|
+
* ALSO carries the PURE intake-flags DERIVATION ({@link deriveIntakeFlags}) —
|
|
10
|
+
* CI's merge-vs-propose POLICY, the load-bearing testable logic the workflow
|
|
11
|
+
* encodes at runtime. The DOCUMENT mode is the resolved `intakeIntegration`
|
|
12
|
+
* (operator/config); author-trust drives ONLY the `--origin-trust` stamp +
|
|
13
|
+
* placement, never the mode.
|
|
12
14
|
*
|
|
13
15
|
* SCOPE FENCE (spec Out-of-Scope): the issue→artifact TRANSFORM engine is
|
|
14
16
|
* `issue-intake`'s (`intake <N>` + its four-outcome dispatch + the per-outcome
|
|
@@ -27,14 +29,23 @@
|
|
|
27
29
|
* (re-)evaluation; an EDITED comment is NOT a trigger (the ID-based
|
|
28
30
|
* `seen=<ids>` watermark suffices). The "post a NEW comment to signal an edit"
|
|
29
31
|
* convention is documented in the workflow so a human knows to re-trigger.
|
|
30
|
-
* -
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
* (
|
|
32
|
+
* - INTAKE-INTEGRATION → the file-emit MODE; AUTHOR-TRUST → PLACEMENT + the
|
|
33
|
+
* STAMP (ADR `untrusted-origin-carries-via-stamp-not-forced-staging`; spec
|
|
34
|
+
* `intake-integration-knob-and-specs-land-in-proposed-rename`; {@link
|
|
35
|
+
* deriveIntakeFlags}): the intake DOCUMENT mode (merge-vs-propose for a
|
|
36
|
+
* task/spec FILE) is the resolved `intakeIntegration ?? integration` — an
|
|
37
|
+
* operator/config choice, a SINGLE value applied to BOTH the task and the spec
|
|
38
|
+
* document, DECOUPLED from the autonomy gates. `autoBuild`/`autoTask` no
|
|
39
|
+
* longer feed the document mode (they mean ONLY "may an agent act
|
|
40
|
+
* autonomously"). Author-trust feeds exactly (1) the `--origin-trust` STAMP
|
|
41
|
+
* (`author_association` not in OWNER/MEMBER/COLLABORATOR ⇒ `untrusted`) and
|
|
42
|
+
* (2) — via that stamp, read by `intake`'s dispatch — which PLACEMENT default
|
|
43
|
+
* (`untrusted*LandIn` vs `*LandIn`) the emitted document lands in. Untrusted
|
|
44
|
+
* safety is the CARRIED stamp (it forces the BUILD transition to a code PR)
|
|
45
|
+
* plus the placement default, NOT a forced document PR. A document therefore
|
|
46
|
+
* merges to `main` regardless of who filed the issue OR whether the gates are
|
|
47
|
+
* on; whether it is reviewed-as-a-PR is purely the `intakeIntegration`
|
|
48
|
+
* operator config, not a trust or autonomy consequence.
|
|
38
49
|
* - INSERTION POINT E (the issue-thread review surface): the review verdict over
|
|
39
50
|
* intake's generated specs/tasks is surfaced into the ISSUE THREAD via the
|
|
40
51
|
* `IssueProvider.postIssueComment` seam (issue thread, by NUMBER — NOT the PR
|
|
@@ -98,77 +109,101 @@ export const TRUSTED_AUTHOR_ASSOCIATIONS = [
|
|
|
98
109
|
* The per-outcome integration flags CI passes to `intake <N>` — the GRANULAR
|
|
99
110
|
* per-type pair (the aggregates `--merge`/`--propose` are not needed because CI
|
|
100
111
|
* always resolves BOTH types explicitly). Each is `'merge'` or `'propose'`.
|
|
112
|
+
*
|
|
113
|
+
* Both {@link spec} and {@link task} are the SAME resolved `intakeIntegration`
|
|
114
|
+
* value (spec `intake-integration-knob-and-specs-land-in-proposed-rename` US #1
|
|
115
|
+
* chose a SINGLE intake-document knob, NOT a per-type split): the pair is kept
|
|
116
|
+
* only because `intake`'s CLI consumes `--merge-task`/`--merge-spec` on two flag
|
|
117
|
+
* axes, not because task and spec can differ. Both are DECOUPLED from the autonomy
|
|
118
|
+
* gates and trust-INDEPENDENT (ADR
|
|
119
|
+
* `untrusted-origin-carries-via-stamp-not-forced-staging`): `autoBuild`/`autoTask`
|
|
120
|
+
* no longer compose into the document mode, and author-trust never did. The trust
|
|
121
|
+
* signal rides only {@link originTrust} (the stamp), which `intake`'s dispatch
|
|
122
|
+
* reads to select the untrusted-side PLACEMENT default. So `spec`/`task` here
|
|
123
|
+
* answer "does the DOCUMENT merge or open a PR", derived purely from the resolved
|
|
124
|
+
* `intakeIntegration` operator config — never from who filed the issue nor whether
|
|
125
|
+
* an agent may act autonomously.
|
|
101
126
|
*/
|
|
102
127
|
export interface IntakeIntegrationFlags {
|
|
103
|
-
/**
|
|
128
|
+
/**
|
|
129
|
+
* The spec outcome's mode → `--merge-spec` / `--propose-spec`. The resolved
|
|
130
|
+
* `intakeIntegration ?? integration` (operator/config), IDENTICAL to
|
|
131
|
+
* {@link task} — the single intake-document knob applied to the spec axis.
|
|
132
|
+
*/
|
|
104
133
|
spec: 'merge' | 'propose';
|
|
105
|
-
/**
|
|
134
|
+
/**
|
|
135
|
+
* The task outcome's mode → `--merge-task` / `--propose-task`. The resolved
|
|
136
|
+
* `intakeIntegration ?? integration` (operator/config), IDENTICAL to
|
|
137
|
+
* {@link spec} — the single intake-document knob applied to the task axis.
|
|
138
|
+
* Neither the autonomy gates nor author-trust force this: an untrusted author's
|
|
139
|
+
* task DOCUMENT merges to `main` exactly like a trusted one when
|
|
140
|
+
* `intakeIntegration` is `merge` (the untrusted safety is the carried
|
|
141
|
+
* {@link originTrust} stamp — which forces the BUILD to a code PR — plus the
|
|
142
|
+
* placement default, NOT a forced document PR).
|
|
143
|
+
*/
|
|
106
144
|
task: 'merge' | 'propose';
|
|
107
145
|
/**
|
|
108
146
|
* The ORIGIN-TRUST verdict CI passes to `intake <N>` via `--origin-trust`
|
|
109
147
|
* (task `untrusted-origin-forces-build-propose`) so the emitted spec/task is
|
|
110
|
-
* STAMPED with how it was born.
|
|
111
|
-
*
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
148
|
+
* STAMPED with how it was born. This is now the SOLE thing author-trust drives
|
|
149
|
+
* on the wire: `intake`'s dispatch reads this stamp to (1) select the
|
|
150
|
+
* untrusted-side PLACEMENT default (`untrusted*LandIn`) and (2) force the later
|
|
151
|
+
* BUILD transition of an untrusted task to a code PR. Derived from the SAME
|
|
152
|
+
* `author_association` case the (gate-derived) modes above see, so it cannot
|
|
153
|
+
* desync. `intake.ts` writes it verbatim onto the frontmatter — it never
|
|
154
|
+
* re-resolves trust (the `intake.ts` ~L296 boundary: author-trust is CI's
|
|
155
|
+
* POLICY, passed IN, not resolved here).
|
|
115
156
|
*/
|
|
116
157
|
originTrust: 'trusted' | 'untrusted';
|
|
117
158
|
}
|
|
118
159
|
|
|
119
|
-
/** The gate state CI composes with author-trust to derive the per-outcome flags. */
|
|
120
|
-
export interface IntakeGateState {
|
|
121
|
-
/**
|
|
122
|
-
* `autoBuild` — whether an agent will AUTO-BUILD an undeclared task next. ON ⇒
|
|
123
|
-
* a task needs a human PR checkpoint NOW (`--propose-task`); OFF ⇒ a human
|
|
124
|
-
* must build it, so it may `--merge-task` (when the author is also trusted).
|
|
125
|
-
*/
|
|
126
|
-
autoBuild: boolean;
|
|
127
|
-
/**
|
|
128
|
-
* `autoTask` — whether an agent will AUTO-TASK an undeclared spec next. ON ⇒ a
|
|
129
|
-
* spec needs a human PR checkpoint NOW (`--propose-spec`); OFF ⇒ a human must
|
|
130
|
-
* task it, so it may `--merge-spec`.
|
|
131
|
-
*/
|
|
132
|
-
autoTask: boolean;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
160
|
/**
|
|
136
|
-
* DERIVE the per-outcome
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
* logic the workflow encodes at runtime (it reads
|
|
140
|
-
*
|
|
161
|
+
* DERIVE the per-outcome file-emit modes + the origin-trust stamp — CI's intake
|
|
162
|
+
* POLICY (ADR `untrusted-origin-carries-via-stamp-not-forced-staging`; spec
|
|
163
|
+
* `intake-integration-knob-and-specs-land-in-proposed-rename` US #1/#2). This is
|
|
164
|
+
* the load-bearing pure logic the workflow encodes at runtime (it reads the
|
|
165
|
+
* resolved `intakeIntegration ?? integration` via `dorfl config --json` for the
|
|
166
|
+
* MODE, and `author_association` off the event payload for the STAMP):
|
|
141
167
|
*
|
|
142
|
-
* - **
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
148
|
-
*
|
|
168
|
+
* - **DOCUMENT mode (spec = task)** — the resolved `intakeIntegration` value, a
|
|
169
|
+
* SINGLE mode applied to BOTH the spec and task document: `merge` ⇒
|
|
170
|
+
* `--merge-spec` + `--merge-task`, `propose` ⇒ `--propose-spec` +
|
|
171
|
+
* `--propose-task`. DECOUPLED from the autonomy gates: `autoBuild`/`autoTask`
|
|
172
|
+
* no longer bite the document mode (they mean ONLY "may an agent act
|
|
173
|
+
* autonomously"). So a repo with `autoBuild: true`/`autoTask: true` +
|
|
174
|
+
* `intakeIntegration` (or `integration`) `merge` MERGES intake documents to
|
|
175
|
+
* `main` — previously impossible (the gates forced a document PR).
|
|
176
|
+
* - **ORIGIN-TRUST stamp** — the ONLY thing author-trust drives: `untrusted`
|
|
177
|
+
* iff the author is not trusted, else `trusted`. `intake`'s dispatch reads
|
|
178
|
+
* this stamp to select the untrusted-side PLACEMENT default and to force the
|
|
179
|
+
* later BUILD transition of an untrusted task to a code PR.
|
|
149
180
|
*
|
|
150
|
-
* So
|
|
151
|
-
* and the
|
|
152
|
-
*
|
|
153
|
-
*
|
|
181
|
+
* So author-trust changes ONLY (a) which folder the document lands in (via the
|
|
182
|
+
* stamp → `untrusted*LandIn`) and (b) the carried stamp — NEVER whether the
|
|
183
|
+
* DOCUMENT is a PR. Whether a document merges or is proposed is now purely the
|
|
184
|
+
* `intakeIntegration` operator config, independent of the gates AND of who filed
|
|
185
|
+
* the issue. The untrusted safety is the stamp (the build-time code PR) plus the
|
|
186
|
+
* placement default, not a forced document PR (the ADR's core move).
|
|
154
187
|
*/
|
|
155
188
|
export function deriveIntakeFlags(options: {
|
|
156
|
-
|
|
189
|
+
intakeIntegration: 'merge' | 'propose';
|
|
157
190
|
authorTrusted: boolean;
|
|
158
191
|
}): IntakeIntegrationFlags {
|
|
159
|
-
const {
|
|
160
|
-
//
|
|
161
|
-
//
|
|
162
|
-
|
|
163
|
-
//
|
|
164
|
-
//
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
//
|
|
192
|
+
const {intakeIntegration, authorTrusted} = options;
|
|
193
|
+
// DOCUMENT mode: the resolved `intakeIntegration ?? integration` (operator/config),
|
|
194
|
+
// a SINGLE value applied to BOTH the spec and the task document. Decoupled from
|
|
195
|
+
// the autonomy gates (ADR untrusted-origin-carries-via-stamp-not-forced-staging;
|
|
196
|
+
// spec intake-integration-knob-and-specs-land-in-proposed-rename): autoBuild/
|
|
197
|
+
// autoTask no longer bite the mode, and author-trust never did. The two axes are
|
|
198
|
+
// kept only because intake's CLI takes --merge-spec/--merge-task separately.
|
|
199
|
+
const spec: 'merge' | 'propose' = intakeIntegration;
|
|
200
|
+
const task: 'merge' | 'propose' = intakeIntegration;
|
|
201
|
+
// ORIGIN-TRUST stamp — the SOLE thing author-trust drives on the wire (task
|
|
202
|
+
// `untrusted-origin-forces-build-propose`): the author-trust verdict collapsed
|
|
203
|
+
// to the value `intake` stamps onto the emitted artifact. `intake`'s dispatch
|
|
204
|
+
// reads it to (1) select the untrusted-side placement default (`untrusted*LandIn`)
|
|
205
|
+
// and (2) force the later BUILD transition of an untrusted task to a code PR. It
|
|
206
|
+
// is NOT a re-resolution of trust (CI already resolved it); it is the verdict
|
|
172
207
|
// being CARRIED so it survives the spec/task merge boundary (the becomes-code
|
|
173
208
|
// checkpoint is not laundered when the file lands on main).
|
|
174
209
|
const originTrust: 'trusted' | 'untrusted' = authorTrusted
|
|
@@ -204,9 +239,13 @@ export function isAuthorTrusted(
|
|
|
204
239
|
* shape itself is config-independent.
|
|
205
240
|
*
|
|
206
241
|
* The per-outcome FLAGS are DERIVED AT RUNTIME by a `bash` step that mirrors
|
|
207
|
-
* {@link deriveIntakeFlags}: it reads the
|
|
208
|
-
* `
|
|
209
|
-
* `--merge-task`/`--propose-task`
|
|
242
|
+
* {@link deriveIntakeFlags}: it reads the resolved `intakeIntegration ??
|
|
243
|
+
* integration` via `dorfl config --json` to set the (config-derived) DOCUMENT
|
|
244
|
+
* mode `--merge-spec`/`--propose-spec` + `--merge-task`/`--propose-task` (a SINGLE
|
|
245
|
+
* mode applied to both), and the event's `author_association` to set ONLY the
|
|
246
|
+
* `--origin-trust` STAMP (which carries the placement + build-PR consequence). The
|
|
247
|
+
* autonomy gates (`autoBuild`/`autoTask`) are NOT read for the document mode (they
|
|
248
|
+
* mean only "may an agent act autonomously"). The same rule that
|
|
210
249
|
* {@link deriveIntakeFlags} unit-tests is what the workflow executes — they cannot
|
|
211
250
|
* desync because the test asserts the SHELL derivation matches the function.
|
|
212
251
|
*/
|
|
@@ -237,15 +276,22 @@ export function generateIntakeWorkflow(config: ResolvedCIConfig): string {
|
|
|
237
276
|
# re-evaluation (a fresh id the watermark catches). There is NO edit-detection /
|
|
238
277
|
# \`updated_at\` / body-hash tracking.
|
|
239
278
|
#
|
|
240
|
-
# AUTHOR-TRUST →
|
|
241
|
-
#
|
|
242
|
-
#
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
#
|
|
246
|
-
#
|
|
247
|
-
#
|
|
248
|
-
#
|
|
279
|
+
# THE DOCUMENT MODE is \`intakeIntegration\`; AUTHOR-TRUST → PLACEMENT + the STAMP:
|
|
280
|
+
# the intake DOCUMENT merge-vs-propose mode is the resolved \`intakeIntegration ??
|
|
281
|
+
# integration\` (an operator/config choice, a SINGLE value applied to BOTH the task
|
|
282
|
+
# and spec document), DECOUPLED from the autonomy gates (spec
|
|
283
|
+
# intake-integration-knob-and-specs-land-in-proposed-rename; ADR
|
|
284
|
+
# untrusted-origin-carries-via-stamp-not-forced-staging). \`autoBuild\`/\`autoTask\`
|
|
285
|
+
# no longer decide the document mode (they mean only "may an agent act
|
|
286
|
+
# autonomously"). Because ANYBODY can file an issue, WHO authored it still matters —
|
|
287
|
+
# but author-trust drives only (1) the \`--origin-trust\` STAMP on the emitted
|
|
288
|
+
# document and (2), via that stamp read by \`intake\`'s dispatch, which PLACEMENT
|
|
289
|
+
# default the document lands in. So an untrusted author's task DOCUMENT MERGES to
|
|
290
|
+
# \`main\` just like a trusted one when \`intakeIntegration\` is \`merge\`; the
|
|
291
|
+
# untrusted safety is the CARRIED stamp (it forces the later BUILD to a code PR)
|
|
292
|
+
# plus the placement default, not a forced document PR. "Merge everything" is
|
|
293
|
+
# \`intakeIntegration: merge\` (or \`integration: merge\`), independent of the gates
|
|
294
|
+
# AND of who filed the issue — so a repo can have autonomy AND merged documents.
|
|
249
295
|
#
|
|
250
296
|
# CI runs IN-PLACE (the CI container IS the isolation): NO --isolated/--remote/
|
|
251
297
|
# registry (laptop-only affordances). The PER-ISSUE concurrency group below
|
|
@@ -291,23 +337,25 @@ permissions:
|
|
|
291
337
|
pull-requests: write
|
|
292
338
|
issues: write
|
|
293
339
|
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
340
|
+
# ── The intake DOCUMENT mode is resolved FROM CONFIG, not carried here ───────
|
|
341
|
+
# CI is NOT a special policy surface (ADR ci-config-policy-and-gate-family §5):
|
|
342
|
+
# it runs the SAME engine config, resolved through flag > env > per-repo > global
|
|
343
|
+
# > default. The SAME dorfl.json the laptop uses applies here. This workflow emits
|
|
344
|
+
# NO DORFL_AUTO_BUILD / DORFL_AUTO_TASK line (ADR
|
|
345
|
+
# untrusted-origin-carries-via-stamp-not-forced-staging: hardcoding them here made
|
|
346
|
+
# the env layer OUTRANK the committed dorfl.json — the shadowing bug). So the env
|
|
347
|
+
# layer carries NO config default; the policy step below READS the resolved intake
|
|
348
|
+
# DOCUMENT mode \`intakeIntegration ?? integration\` via \`dorfl config --json\` (the
|
|
349
|
+
# mechanism \`advance\` already uses), so your committed dorfl.json wins (then the
|
|
350
|
+
# global config, then the built-in default \`propose\`). To land intake documents on
|
|
351
|
+
# main, set \`intakeIntegration: merge\` (or \`integration: merge\`) in dorfl.json
|
|
352
|
+
# (applies everywhere) — NOT by re-running install-ci (ADR §6: install-ci is
|
|
353
|
+
# one-time). The autonomy gates \`autoBuild\`/\`autoTask\` are DECOUPLED from the
|
|
354
|
+
# document mode (spec intake-integration-knob-and-specs-land-in-proposed-rename):
|
|
355
|
+
# they gate autonomy only and are NOT read here. \`intake\` itself is GATE-FREE (the
|
|
356
|
+
# explicit invocation is its own authorization); CI READS only the resolved
|
|
357
|
+
# \`intakeIntegration\`/\`integration\` to DERIVE the merge-vs-propose document flags
|
|
358
|
+
# below (the merge-vs-propose POLICY).
|
|
311
359
|
|
|
312
360
|
jobs:
|
|
313
361
|
intake:
|
|
@@ -322,16 +370,23 @@ jobs:
|
|
|
322
370
|
fetch-depth: 0
|
|
323
371
|
- uses: ./.github/actions/dorfl-setup${setupWith}
|
|
324
372
|
|
|
325
|
-
- name: derive the
|
|
373
|
+
- name: derive the intake DOCUMENT mode (intakeIntegration) + the origin-trust stamp (author-trust)
|
|
326
374
|
id: policy
|
|
327
|
-
# The
|
|
375
|
+
# The intake POLICY, executed at runtime — the SAME rule
|
|
328
376
|
# \`deriveIntakeFlags\` unit-tests (they cannot desync; the test asserts this
|
|
329
|
-
# shell matches the function)
|
|
330
|
-
#
|
|
331
|
-
#
|
|
332
|
-
#
|
|
333
|
-
#
|
|
334
|
-
#
|
|
377
|
+
# shell matches the function). The DOCUMENT mode is the resolved
|
|
378
|
+
# \`intakeIntegration ?? integration\` (operator/config), DECOUPLED from the
|
|
379
|
+
# autonomy gates; author-trust drives ONLY the stamp + placement (ADR
|
|
380
|
+
# untrusted-origin-carries-via-stamp-not-forced-staging; spec
|
|
381
|
+
# intake-integration-knob-and-specs-land-in-proposed-rename):
|
|
382
|
+
# * DOCUMENT mode (spec = task) — config-derived: --merge-* iff the
|
|
383
|
+
# resolved intakeIntegration is merge, else --propose-*. A SINGLE
|
|
384
|
+
# value applied to BOTH the spec and task document. autoBuild/
|
|
385
|
+
# autoTask are NOT read (they gate autonomy only).
|
|
386
|
+
# * ORIGIN-TRUST stamp — the ONLY thing author-trust drives: --origin-trust
|
|
387
|
+
# untrusted iff the author is not OWNER/MEMBER/COLLABORATOR, which
|
|
388
|
+
# \`intake\`'s dispatch reads to select the untrusted PLACEMENT
|
|
389
|
+
# default and to force the later BUILD to a code PR.
|
|
335
390
|
# author_association comes from the COMMENT on an \`issue_comment\` event,
|
|
336
391
|
# else the ISSUE on an \`issues\` event — read straight off the payload, no
|
|
337
392
|
# extra API call.
|
|
@@ -340,37 +395,53 @@ jobs:
|
|
|
340
395
|
run: |
|
|
341
396
|
set -euo pipefail
|
|
342
397
|
|
|
343
|
-
#
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
398
|
+
# Read the RESOLVED intake DOCUMENT mode from the committed config via
|
|
399
|
+
# \`dorfl config --json\` (the mechanism \`advance\` already uses), NOT a
|
|
400
|
+
# hardcoded DORFL_* env (ADR
|
|
401
|
+
# untrusted-origin-carries-via-stamp-not-forced-staging: an env default
|
|
402
|
+
# would OUTRANK dorfl.json — the shadowing bug). In-place, so the
|
|
403
|
+
# resolution chain reads THIS repo's dorfl.json exactly like the laptop.
|
|
404
|
+
# \`intakeIntegration\` is OPTIONAL (unset ⇒ falls back to \`integration\`),
|
|
405
|
+
# so read \`.intakeIntegration // .integration\` in ONE jq expression — the
|
|
406
|
+
# shell twin of the \`intakeIntegration ?? integration\` the CLI applies.
|
|
407
|
+
# The autonomy gates (autoBuild/autoTask) are DECOUPLED from the document
|
|
408
|
+
# mode (spec intake-integration-knob-and-specs-land-in-proposed-rename):
|
|
409
|
+
# they gate autonomy only and are NOT read here.
|
|
410
|
+
config_json="$(dorfl config --json)"
|
|
411
|
+
intake_integration="$(echo "\${config_json}" | jq -r '.intakeIntegration // .integration')"
|
|
412
|
+
|
|
413
|
+
# DOCUMENT mode: config-derived from the single \`intakeIntegration ??
|
|
414
|
+
# integration\` value, applied to BOTH the spec and task document (US #1
|
|
415
|
+
# chose one intake knob, not a per-type split). merge ⇒ --merge-*,
|
|
416
|
+
# else --propose-*. Author-trust does NOT bite the mode (ADR
|
|
417
|
+
# untrusted-origin-carries-via-stamp-not-forced-staging): an untrusted
|
|
418
|
+
# author's DOCUMENT merges just like a trusted one; the untrusted safety
|
|
419
|
+
# is the stamp + placement below, not a document PR.
|
|
420
|
+
if [ "\${intake_integration}" = "merge" ]; then
|
|
347
421
|
spec_flag="--merge-spec"
|
|
422
|
+
task_flag="--merge-task"
|
|
423
|
+
else
|
|
424
|
+
spec_flag="--propose-spec"
|
|
425
|
+
task_flag="--propose-task"
|
|
348
426
|
fi
|
|
349
427
|
|
|
350
428
|
# Author-trust: TRUSTED iff OWNER/MEMBER/COLLABORATOR (admin / write-
|
|
351
429
|
# collaborator — the whole signal). Anything else (incl. empty) is
|
|
352
|
-
# UNTRUSTED
|
|
430
|
+
# UNTRUSTED. It drives ONLY the origin-trust stamp below (NOT the modes).
|
|
353
431
|
trusted="false"
|
|
354
432
|
case "\${AUTHOR_ASSOCIATION:-}" in
|
|
355
433
|
OWNER|MEMBER|COLLABORATOR) trusted="true" ;;
|
|
356
434
|
esac
|
|
357
435
|
|
|
358
|
-
#
|
|
359
|
-
#
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
#
|
|
367
|
-
# derived from the SAME \${trusted} case above (one author-trust read,
|
|
368
|
-
# two consumers — the task/spec modes AND the stamp — so they cannot
|
|
369
|
-
# desync). \`intake\` STAMPS this onto the emitted spec/task frontmatter
|
|
370
|
-
# (origin: issue + originTrust: <value>); it does NOT re-resolve trust
|
|
371
|
-
# (that is CI's policy, passed IN). The stamp SURVIVES the merge boundary
|
|
372
|
-
# so a later auto-task/auto-build of an untrusted-origin artifact still
|
|
373
|
-
# forces a human becomes-code checkpoint (the laundering gap is closed).
|
|
436
|
+
# ORIGIN-TRUST stamp — the SOLE thing author-trust drives on the wire
|
|
437
|
+
# (task untrusted-origin-forces-build-propose). \`intake\` STAMPS this
|
|
438
|
+
# onto the emitted spec/task frontmatter (origin: issue + originTrust:
|
|
439
|
+
# <value>); its dispatch reads the stamp to (1) select the untrusted-side
|
|
440
|
+
# PLACEMENT default (\`untrusted*LandIn\`) and (2) force the later BUILD of
|
|
441
|
+
# an untrusted task to a code PR. It does NOT re-resolve trust (that is
|
|
442
|
+
# CI's policy, passed IN). The stamp SURVIVES the merge boundary so a
|
|
443
|
+
# later auto-task/auto-build of an untrusted-origin artifact still forces
|
|
444
|
+
# a human becomes-code checkpoint (the laundering gap is closed).
|
|
374
445
|
if [ "\${trusted}" = "true" ]; then
|
|
375
446
|
origin_trust_flag="--origin-trust=trusted"
|
|
376
447
|
else
|
|
@@ -380,12 +451,15 @@ jobs:
|
|
|
380
451
|
echo "spec_flag=\${spec_flag}" >> "\$GITHUB_OUTPUT"
|
|
381
452
|
echo "task_flag=\${task_flag}" >> "\$GITHUB_OUTPUT"
|
|
382
453
|
echo "origin_trust_flag=\${origin_trust_flag}" >> "\$GITHUB_OUTPUT"
|
|
383
|
-
echo "intake policy: author_association='\${AUTHOR_ASSOCIATION:-}' trusted=\${trusted} → \${spec_flag} \${task_flag} \${origin_trust_flag}"
|
|
454
|
+
echo "intake policy: intakeIntegration='\${intake_integration}' author_association='\${AUTHOR_ASSOCIATION:-}' trusted=\${trusted} → \${spec_flag} \${task_flag} \${origin_trust_flag}"
|
|
384
455
|
|
|
385
456
|
- name: intake the issue (four-outcome dispatch; surfaces the review verdict into the thread)
|
|
386
457
|
# In-place in this checkout (no --isolated/--remote): the CI container IS
|
|
387
458
|
# the isolation. EXPLICIT \`intake <N>\`, never a bare slug. The per-outcome
|
|
388
|
-
# flags carry the
|
|
459
|
+
# flags carry the (intakeIntegration-derived) document modes + the
|
|
460
|
+
# origin-trust stamp derived above (author-trust drives only the stamp +
|
|
461
|
+
# placement, not the document merge-vs-propose; the autonomy gates drive
|
|
462
|
+
# neither). \`intake\` runs the
|
|
389
463
|
# lone-task review/edit loop and posts its findings as questions back into
|
|
390
464
|
# THIS issue thread (insertion point E) through the issue-comment seam —
|
|
391
465
|
# CI surfaces E by invoking intake; it adds no new review mechanism.
|
|
@@ -498,29 +572,33 @@ export function validateIntakeWorkflow(text: string): IntakeTriggerValidation {
|
|
|
498
572
|
text,
|
|
499
573
|
), 'author-trust must be OWNER/MEMBER/COLLABORATOR (admin / write-collaborator ' +
|
|
500
574
|
'— the whole signal; Decision 1).');
|
|
501
|
-
//
|
|
502
|
-
//
|
|
503
|
-
//
|
|
575
|
+
// The DOCUMENT mode is intakeIntegration-DERIVED (spec
|
|
576
|
+
// intake-integration-knob-and-specs-land-in-proposed-rename; the autonomy gates
|
|
577
|
+
// no longer bite it): the derivation must be able to emit both task modes
|
|
578
|
+
// (merge iff the resolved intakeIntegration is merge, else propose).
|
|
504
579
|
require('derives-propose-task', /--propose-task\b/.test(
|
|
505
580
|
operative,
|
|
506
581
|
), 'the policy derivation must be able to emit `--propose-task` (the ' +
|
|
507
|
-
'
|
|
582
|
+
'intakeIntegration-propose path).');
|
|
508
583
|
require('derives-merge-task', /--merge-task\b/.test(
|
|
509
584
|
operative,
|
|
510
585
|
), 'the policy derivation must be able to emit `--merge-task` (the ' +
|
|
511
|
-
'
|
|
512
|
-
|
|
586
|
+
'intakeIntegration-merge path; a task DOCUMENT merges regardless of the ' +
|
|
587
|
+
'autonomy gates or author-trust).');
|
|
588
|
+
// --merge-spec is emitted when the resolved intakeIntegration is merge (the
|
|
589
|
+
// SAME single value as the task; the gates do not bite it).
|
|
513
590
|
require('derives-merge-spec', /--merge-spec\b/.test(
|
|
514
591
|
operative,
|
|
515
|
-
), 'the policy derivation must be able to emit `--merge-spec` (
|
|
516
|
-
'
|
|
592
|
+
), 'the policy derivation must be able to emit `--merge-spec` (the ' +
|
|
593
|
+
'intakeIntegration-merge path).');
|
|
517
594
|
require('derives-propose-spec', /--propose-spec\b/.test(
|
|
518
595
|
operative,
|
|
519
|
-
), 'the policy derivation must be able to emit `--propose-spec` (
|
|
596
|
+
), 'the policy derivation must be able to emit `--propose-spec` (the ' +
|
|
597
|
+
'intakeIntegration-propose path).');
|
|
520
598
|
// ORIGIN-TRUST stamp (task untrusted-origin-forces-build-propose): the shell
|
|
521
|
-
// must derive `--origin-trust <trusted|untrusted>` from the
|
|
522
|
-
//
|
|
523
|
-
// artifact is stamped (the stamp
|
|
599
|
+
// must derive `--origin-trust <trusted|untrusted>` from the author-trust case
|
|
600
|
+
// (independent of the document mode) and pass it to `intake` so the emitted
|
|
601
|
+
// artifact is stamped (the stamp is the SOLE thing author-trust drives).
|
|
524
602
|
require('derives-origin-trust-untrusted', /--origin-trust=untrusted\b/.test(
|
|
525
603
|
operative,
|
|
526
604
|
), 'the policy derivation must emit `--origin-trust=untrusted` for a non-trusted ' +
|
|
@@ -533,13 +611,55 @@ export function validateIntakeWorkflow(text: string): IntakeTriggerValidation {
|
|
|
533
611
|
operative,
|
|
534
612
|
), 'the intake invocation must pass the derived `--origin-trust` flag (the ' +
|
|
535
613
|
'stamp must reach `dorfl intake`).');
|
|
536
|
-
// The derivation must
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
614
|
+
// The derivation must read the RESOLVED intake DOCUMENT mode via `dorfl config
|
|
615
|
+
// --json` (the mechanism `advance` uses), NOT a hardcoded DORFL_* env (ADR
|
|
616
|
+
// untrusted-origin-carries-via-stamp-not-forced-staging — the shadowing bug).
|
|
617
|
+
require('reads-config-json', /dorfl config --json/.test(
|
|
618
|
+
operative,
|
|
619
|
+
), 'the policy derivation must read the resolved config via ' +
|
|
620
|
+
'`dorfl config --json` (as `advance` does), so a committed `dorfl.json` ' +
|
|
621
|
+
'mode is honored in CI (not shadowed by a hardcoded env).');
|
|
622
|
+
// The DOCUMENT mode is the resolved `intakeIntegration ?? integration` (spec
|
|
623
|
+
// intake-integration-knob-and-specs-land-in-proposed-rename): the derivation
|
|
624
|
+
// must read `.intakeIntegration` with a `.integration` FALLBACK (the shell twin
|
|
625
|
+
// of `intakeIntegration ?? integration`), NOT the autonomy gates. Pin BOTH the
|
|
626
|
+
// key and the fallback so a regression to a gate-derived mode is caught.
|
|
627
|
+
require('reads-intake-integration', /\.intakeIntegration\b/.test(
|
|
628
|
+
operative,
|
|
629
|
+
), 'the policy derivation must read the resolved `intakeIntegration` mode (jq ' +
|
|
630
|
+
'`.intakeIntegration` off `dorfl config --json`) for the document mode.');
|
|
631
|
+
require('intake-integration-falls-back-to-integration', /\.intakeIntegration\s*\/\/\s*\.integration/.test(
|
|
632
|
+
operative,
|
|
633
|
+
), 'the policy derivation must fall back to `.integration` when ' +
|
|
634
|
+
'`intakeIntegration` is unset (jq `.intakeIntegration // .integration`, the ' +
|
|
635
|
+
'shell twin of `intakeIntegration ?? integration`).');
|
|
636
|
+
// The DOCUMENT mode must NOT be derived from the autonomy gates (spec
|
|
637
|
+
// intake-integration-knob-and-specs-land-in-proposed-rename: autoBuild/autoTask
|
|
638
|
+
// gate autonomy ONLY, never the document PR-mode). The mode branch must not read
|
|
639
|
+
// `.autoBuild` / `.autoTask` off the config json.
|
|
640
|
+
require('mode-not-gate-derived', !/\.auto(?:Build|Task)\b/.test(
|
|
641
|
+
operative,
|
|
642
|
+
), 'the intake DOCUMENT mode must be derived from `intakeIntegration ?? ' +
|
|
643
|
+
'integration`, NOT the autonomy gates — the derivation must not read ' +
|
|
644
|
+
'`.autoBuild` / `.autoTask` (they gate autonomy only; spec ' +
|
|
645
|
+
'intake-integration-knob-and-specs-land-in-proposed-rename).');
|
|
646
|
+
// ANTI-REGRESSION (ADR untrusted-origin-carries-via-stamp-not-forced-staging;
|
|
647
|
+
// spec US #12): the workflow must NOT emit a `DORFL_AUTO_BUILD:` /
|
|
648
|
+
// `DORFL_AUTO_TASK:` env ASSIGNMENT. The env layer OUTRANKS per-repo config, so
|
|
649
|
+
// a hardcoded default here SHADOWS the committed `dorfl.json` gates (the bug this
|
|
650
|
+
// task fixes). Mirrors `advance-lifecycle-template.ts`'s `no-gate-env-auto-build`
|
|
651
|
+
// / `no-gate-env-auto-task`. Checked over the OPERATIVE (non-comment) lines so
|
|
652
|
+
// the header comment that NAMES these keys is not a false positive.
|
|
653
|
+
require('no-gate-env-auto-build', !/DORFL_AUTO_BUILD\s*:/.test(
|
|
654
|
+
operative,
|
|
655
|
+
), 'the workflow must NOT emit a `DORFL_AUTO_BUILD:` env assignment (env ' +
|
|
656
|
+
'carries no defaults; the gate is resolved from per-repo config / built-in ' +
|
|
657
|
+
'default — else the env SHADOWS the committed dorfl.json).');
|
|
658
|
+
require('no-gate-env-auto-task', !/DORFL_AUTO_TASK\s*:/.test(
|
|
659
|
+
operative,
|
|
660
|
+
), 'the workflow must NOT emit a `DORFL_AUTO_TASK:` env assignment (env ' +
|
|
661
|
+
'carries no defaults; the gate is resolved from per-repo config / built-in ' +
|
|
662
|
+
'default — else the env SHADOWS the committed dorfl.json).');
|
|
543
663
|
|
|
544
664
|
// --- Insertion point E: the issue-thread review surface ---------------------
|
|
545
665
|
// E is REUSED via `intake` (which runs the lone-task review and posts to the
|