@macedon-technologies/batman 1.17.2 → 1.18.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 (50) hide show
  1. package/.jsii +205 -26
  2. package/.swamp.yaml +6 -0
  3. package/API.md +164 -1
  4. package/CLAUDE.md +44 -0
  5. package/architecture-rules/README.md +42 -0
  6. package/architecture-rules/check.ts +162 -0
  7. package/architecture-rules/rules.json +3 -0
  8. package/architecture-rules/tsconfig.json +14 -0
  9. package/attestation/manifest.ts +40 -0
  10. package/attestation/tsconfig.json +13 -0
  11. package/attestation/verify.ts +142 -0
  12. package/build-tools/scan-generated-workflows.ts +18 -11
  13. package/lib/Application.js +6 -1
  14. package/lib/BatmanProject.js +8 -2
  15. package/lib/BootcampProject.js +8 -2
  16. package/lib/CodeOwnersFile.js +1 -1
  17. package/lib/ProductionDeployWorkflow.js +1 -1
  18. package/lib/ProjenWorkflowHardening.d.ts +18 -0
  19. package/lib/ProjenWorkflowHardening.js +59 -0
  20. package/lib/SoftwareFactoryArchitectureRules.d.ts +10 -0
  21. package/lib/SoftwareFactoryArchitectureRules.js +245 -0
  22. package/lib/SoftwareFactoryAttestation.d.ts +18 -0
  23. package/lib/SoftwareFactoryAttestation.js +221 -0
  24. package/lib/SoftwareFactoryFiles.d.ts +45 -0
  25. package/lib/SoftwareFactoryFiles.js +102 -0
  26. package/lib/SoftwareFactoryModels.d.ts +25 -0
  27. package/lib/SoftwareFactoryModels.js +306 -0
  28. package/lib/SoftwareFactoryOpenSpecGates.d.ts +8 -0
  29. package/lib/SoftwareFactoryOpenSpecGates.js +75 -0
  30. package/lib/SoftwareFactoryWorkflows.d.ts +28 -0
  31. package/lib/SoftwareFactoryWorkflows.js +326 -0
  32. package/lib/StagingDeployWorkflow.js +1 -1
  33. package/lib/WorkflowActionVersions.d.ts +31 -1
  34. package/lib/WorkflowActionVersions.js +5 -1
  35. package/lib/WorkflowAutoDiscover.js +1 -1
  36. package/lib/index.d.ts +1 -0
  37. package/lib/index.js +2 -1
  38. package/lib/utils.js +29 -1
  39. package/models/@goodcraft/github/platform-common-pr.yaml +10 -0
  40. package/models/@swamp/software-factory/platform-common-factory.yaml +240 -0
  41. package/models/@webframp/github/platform-common-issues.yaml +8 -0
  42. package/models/command/shell/platform-common-shell-architecture.yaml +8 -0
  43. package/models/command/shell/platform-common-shell-manifest.yaml +8 -0
  44. package/models/command/shell/platform-common-shell.yaml +8 -0
  45. package/openspec-gates/check-tasks-complete.ts +44 -0
  46. package/openspec-gates/tsconfig.json +13 -0
  47. package/package.json +6 -1
  48. package/workflows/workflow-openspec-plan-verify.yaml +51 -0
  49. package/workflows/workflow-openspec-tasks-verify.yaml +51 -0
  50. package/workflows/workflow-pre-pr-verify.yaml +118 -0
@@ -0,0 +1,306 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SoftwareFactoryModels = void 0;
4
+ const crypto_1 = require("crypto");
5
+ const projen_1 = require("projen");
6
+ /**
7
+ * Seed swamp model instances wiring the `@swamp/software-factory` state
8
+ * machine to this repo's GitHub issues/PRs and shell steps. These are
9
+ * `SampleFile`s (written once, then owned by the team) since the factory's
10
+ * stages/gates are the kind of thing a team tunes for its own workflow.
11
+ */
12
+ class SoftwareFactoryModels {
13
+ constructor(project, options) {
14
+ const { repoName, githubOwner, baseBranch } = options;
15
+ const repo = `${githubOwner}/${repoName}`;
16
+ const issuesModel = `${repoName}-issues`;
17
+ const prModel = `${repoName}-pr`;
18
+ const shellModel = `${repoName}-shell`;
19
+ const shellArchitectureModel = `${repoName}-shell-architecture`;
20
+ const shellManifestModel = `${repoName}-shell-manifest`;
21
+ const secretsVault = `${repoName}-secrets`;
22
+ const factoryModel = `${repoName}-factory`;
23
+ new projen_1.SampleFile(project, `models/@goodcraft/github/${prModel}.yaml`, {
24
+ contents: `type: '@goodcraft/github'
25
+ typeVersion: 2026.06.14.1
26
+ id: ${(0, crypto_1.randomUUID)()}
27
+ name: ${prModel}
28
+ version: 1
29
+ tags: {}
30
+ globalArguments:
31
+ token: '\${{ vault.get(${secretsVault}, GITHUB_TOKEN) }}'
32
+ owner: ${githubOwner}
33
+ methods: {}
34
+ `,
35
+ });
36
+ new projen_1.SampleFile(project, `models/@webframp/github/${issuesModel}.yaml`, {
37
+ contents: `type: '@webframp/github'
38
+ typeVersion: 2026.08.28.1
39
+ id: ${(0, crypto_1.randomUUID)()}
40
+ name: ${issuesModel}
41
+ version: 1
42
+ tags: {}
43
+ globalArguments: {}
44
+ methods: {}
45
+ `,
46
+ });
47
+ for (const name of [shellModel, shellArchitectureModel, shellManifestModel]) {
48
+ new projen_1.SampleFile(project, `models/command/shell/${name}.yaml`, {
49
+ contents: `type: command/shell
50
+ typeVersion: 2026.02.09.1
51
+ id: ${(0, crypto_1.randomUUID)()}
52
+ name: ${name}
53
+ version: 1
54
+ tags: {}
55
+ globalArguments: {}
56
+ methods: {}
57
+ `,
58
+ });
59
+ }
60
+ new projen_1.SampleFile(project, `models/@swamp/software-factory/${factoryModel}.yaml`, {
61
+ contents: `type: '@swamp/software-factory'
62
+ typeVersion: 2026.06.24.1
63
+ id: ${(0, crypto_1.randomUUID)()}
64
+ name: ${factoryModel}
65
+ version: 1
66
+ tags: {}
67
+ reports:
68
+ require:
69
+ - name: "@swamp/software-factory/work-item-summary"
70
+ methods: [summary]
71
+ globalArguments:
72
+ stages:
73
+ - id: intake
74
+ initial: true
75
+ description: >-
76
+ Fetch the GitHub issue for this work item (workItem is the bare
77
+ issue number) and record its context.
78
+ work:
79
+ mode: interactive
80
+ systemPrompt: >-
81
+ Fetch the GitHub issue numbered \${{ self.workItem }} for
82
+ ${repo} using the ${issuesModel} model
83
+ (list_issues, then swamp data query filtered by number — see the
84
+ ${issuesModel} data), and record the issue-context artifact.
85
+ artifacts:
86
+ - name: issue-context
87
+ schema:
88
+ type: object
89
+ required: [number, title, url]
90
+ properties:
91
+ number: { type: integer }
92
+ title: { type: string, minLength: 1 }
93
+ body: { type: string }
94
+ url: { type: string, minLength: 1 }
95
+ labels: { type: array, items: { type: string } }
96
+ transitions:
97
+ - name: submit
98
+ to: planning
99
+ gates:
100
+ - type: artifact-exists
101
+ config: { artifact: issue-context }
102
+
103
+ - id: planning
104
+ description: >-
105
+ Turn the issue into a real OpenSpec change (proposal, design,
106
+ specs, tasks) — OpenSpec's task list is the plan, not a
107
+ freeform artifact.
108
+ work:
109
+ mode: interactive
110
+ systemPrompt: >-
111
+ Using the recorded issue-context artifact, use the
112
+ openspec-propose skill to create a complete OpenSpec change
113
+ (proposal.md, design.md, specs with deltas, tasks.md) for this
114
+ work item under openspec/changes/<changeSlug>/ — do not write a
115
+ freeform plan instead of this. Then record the plan artifact
116
+ with the change's slug and a one-line summary.
117
+ artifacts:
118
+ - name: plan
119
+ schema:
120
+ type: object
121
+ required: [changeSlug, summary]
122
+ properties:
123
+ changeSlug: { type: string, minLength: 1 }
124
+ summary: { type: string, minLength: 1 }
125
+ transitions:
126
+ - name: submit
127
+ to: openspec-plan-verify
128
+ gates:
129
+ - type: artifact-exists
130
+ config: { artifact: plan }
131
+
132
+ - id: openspec-plan-verify
133
+ description: >-
134
+ Verify the OpenSpec change is real and well-formed (not just an
135
+ artifact claiming one exists) before implementation starts.
136
+ work:
137
+ mode: workflow
138
+ workflow:
139
+ name: openspec-plan-verify
140
+ inputs:
141
+ changeSlug: '\${{ data.latest(self.name, "artifact-plan").payload.changeSlug }}'
142
+ resultEvidence: plan-verify-run
143
+ transitions:
144
+ - name: pass
145
+ to: implementing
146
+ gates:
147
+ - type: workflow-succeeded
148
+ config: { workflow: openspec-plan-verify }
149
+ - name: fail
150
+ to: planning
151
+ gates:
152
+ - type: evidence-recorded
153
+ config: { name: plan-verify-run, requireField: { status: failed } }
154
+
155
+ - id: implementing
156
+ description: Implement the OpenSpec change's tasks on a branch.
157
+ work:
158
+ mode: interactive
159
+ systemPrompt: >-
160
+ Use the openspec-apply-change skill against the change recorded
161
+ in the plan artifact's changeSlug — work through its tasks.md
162
+ one task at a time, checking each off (\`- [ ]\` -> \`- [x]\`) as
163
+ you complete it, until every task is done. Commit the work on a
164
+ feature branch. Record the change-summary artifact and the
165
+ change-request evidence (branch name and head commit sha — no
166
+ PR yet, that happens after pre-PR verification passes).
167
+ artifacts:
168
+ - name: change-summary
169
+ schema:
170
+ type: object
171
+ required: [summary, headSha]
172
+ properties:
173
+ summary: { type: string, minLength: 1 }
174
+ headSha: { type: string, minLength: 7 }
175
+ evidence:
176
+ - name: change-request
177
+ schema:
178
+ type: object
179
+ required: [branch, headSha]
180
+ properties:
181
+ branch: { type: string, minLength: 1 }
182
+ headSha: { type: string, minLength: 7 }
183
+ url: { type: string }
184
+ transitions:
185
+ - name: submit
186
+ to: openspec-tasks-verify
187
+ gates:
188
+ - type: artifact-exists
189
+ config: { artifact: change-summary }
190
+ - type: evidence-recorded
191
+ config: { name: change-request }
192
+
193
+ - id: openspec-tasks-verify
194
+ description: >-
195
+ Verify every task in the OpenSpec change is actually checked off
196
+ before pre-PR verification runs.
197
+ work:
198
+ mode: workflow
199
+ workflow:
200
+ name: openspec-tasks-verify
201
+ inputs:
202
+ changeSlug: '\${{ data.latest(self.name, "artifact-plan").payload.changeSlug }}'
203
+ resultEvidence: tasks-verify-run
204
+ transitions:
205
+ - name: pass
206
+ to: pre-pr-verify
207
+ gates:
208
+ - type: workflow-succeeded
209
+ config: { workflow: openspec-tasks-verify }
210
+ - name: fail
211
+ to: implementing
212
+ gates:
213
+ - type: evidence-recorded
214
+ config: { name: tasks-verify-run, requireField: { status: failed } }
215
+
216
+ - id: pre-pr-verify
217
+ description: >-
218
+ Pre-PR verification: build/synth/test/lint plus the architecture
219
+ rule registry, run before the PR is opened rather than relying on
220
+ CI after the fact.
221
+ work:
222
+ mode: workflow
223
+ workflow:
224
+ name: pre-pr-verify
225
+ inputs:
226
+ commit: '\${{ data.latest(self.name, "evidence-change-request").payload.headSha }}'
227
+ branch: '\${{ data.latest(self.name, "evidence-change-request").payload.branch }}'
228
+ resultEvidence: verify-run
229
+ transitions:
230
+ - name: pass
231
+ to: open-pr
232
+ gates:
233
+ - type: workflow-succeeded
234
+ config: { workflow: pre-pr-verify }
235
+ - name: fail
236
+ to: implementing
237
+ gates:
238
+ - type: evidence-recorded
239
+ config: { name: verify-run, requireField: { status: failed } }
240
+
241
+ - id: open-pr
242
+ description: Open the pull request now that pre-PR verification has passed.
243
+ work:
244
+ mode: interactive
245
+ systemPrompt: >-
246
+ Before opening the PR, fetch the real pre-pr-verify attestation
247
+ with \`swamp data get --workflow pre-pr-verify
248
+ report-swamp-verification-attestation --json\` and include its
249
+ \`content\` verbatim (as a markdown block, e.g. under a "Pre-PR
250
+ verification" heading) in the PR body — never just assert in
251
+ prose that checks passed. Also fetch this same run's manifest step
252
+ output (\`swamp data query 'modelName == "${shellManifestModel}" &&
253
+ name == "result"' --select attributes.stdout --json\`, take the
254
+ latest) and append it to the PR body as
255
+ \`<!-- attestation-manifest {"commit": "<headSha>", "status":
256
+ "succeeded", ...the manifest step's JSON, spread in...} -->\` — an
257
+ HTML comment so it's invisible when rendered but still present in
258
+ the raw body text verify-attestation.yml reads via the GitHub API.
259
+ Also fetch the plan artifact's changeSlug and include an explicit
260
+ "OpenSpec change" line in the PR body naming it and its path
261
+ (\`openspec/changes/<changeSlug>/\`) — every PR the factory opens
262
+ must call out which OpenSpec change(s) drove the session, not
263
+ just imply it. If planning cycled through more than one change
264
+ (a rework loop produced a new changeSlug), list every distinct
265
+ changeSlug recorded across the run's plan artifact versions, not
266
+ only the latest. Then call ${prModel}'s openPr method
267
+ (repo=${repo}, head=<the branch recorded in
268
+ change-request evidence>, base=${baseBranch}, dryRun=false), with
269
+ title/body referencing the intake stage's issue number, the
270
+ OpenSpec change(s), the attestation block, and the manifest
271
+ comment, then record the pr-opened evidence with the returned PR
272
+ url and number.
273
+ evidence:
274
+ - name: pr-opened
275
+ schema:
276
+ type: object
277
+ required: [url, number]
278
+ properties:
279
+ url: { type: string, minLength: 1 }
280
+ number: { type: integer }
281
+ transitions:
282
+ - name: submit
283
+ to: done
284
+ gates:
285
+ - type: evidence-recorded
286
+ config: { name: pr-opened }
287
+
288
+ - id: done
289
+ terminal: true
290
+
291
+ - id: aborted
292
+ terminal: true
293
+
294
+ globalTransitions:
295
+ - name: abort
296
+ to: aborted
297
+ gates:
298
+ - type: human-approval
299
+ config: { id: abort-confirmation }
300
+ methods: {}
301
+ `,
302
+ });
303
+ }
304
+ }
305
+ exports.SoftwareFactoryModels = SoftwareFactoryModels;
306
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU29mdHdhcmVGYWN0b3J5TW9kZWxzLmpzIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiLi4vc3JjL1NvZnR3YXJlRmFjdG9yeU1vZGVscy50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOzs7QUFBQSxtQ0FBb0M7QUFDcEMsbUNBQW9DO0FBcUJwQzs7Ozs7R0FLRztBQUNILE1BQWEscUJBQXFCO0lBQ2hDLFlBQVksT0FBb0IsRUFBRSxPQUFxQztRQUNyRSxNQUFNLEVBQUUsUUFBUSxFQUFFLFdBQVcsRUFBRSxVQUFVLEVBQUUsR0FBRyxPQUFPLENBQUM7UUFDdEQsTUFBTSxJQUFJLEdBQUcsR0FBRyxXQUFXLElBQUksUUFBUSxFQUFFLENBQUM7UUFDMUMsTUFBTSxXQUFXLEdBQUcsR0FBRyxRQUFRLFNBQVMsQ0FBQztRQUN6QyxNQUFNLE9BQU8sR0FBRyxHQUFHLFFBQVEsS0FBSyxDQUFDO1FBQ2pDLE1BQU0sVUFBVSxHQUFHLEdBQUcsUUFBUSxRQUFRLENBQUM7UUFDdkMsTUFBTSxzQkFBc0IsR0FBRyxHQUFHLFFBQVEscUJBQXFCLENBQUM7UUFDaEUsTUFBTSxrQkFBa0IsR0FBRyxHQUFHLFFBQVEsaUJBQWlCLENBQUM7UUFDeEQsTUFBTSxZQUFZLEdBQUcsR0FBRyxRQUFRLFVBQVUsQ0FBQztRQUMzQyxNQUFNLFlBQVksR0FBRyxHQUFHLFFBQVEsVUFBVSxDQUFDO1FBRTNDLElBQUksbUJBQVUsQ0FBQyxPQUFPLEVBQUUsNEJBQTRCLE9BQU8sT0FBTyxFQUFFO1lBQ2xFLFFBQVEsRUFBRTs7TUFFVixJQUFBLG1CQUFVLEdBQUU7UUFDVixPQUFPOzs7OzJCQUlZLFlBQVk7V0FDNUIsV0FBVzs7Q0FFckI7U0FDSSxDQUFDLENBQUM7UUFFSCxJQUFJLG1CQUFVLENBQUMsT0FBTyxFQUFFLDJCQUEyQixXQUFXLE9BQU8sRUFBRTtZQUNyRSxRQUFRLEVBQUU7O01BRVYsSUFBQSxtQkFBVSxHQUFFO1FBQ1YsV0FBVzs7Ozs7Q0FLbEI7U0FDSSxDQUFDLENBQUM7UUFFSCxLQUFLLE1BQU0sSUFBSSxJQUFJLENBQUMsVUFBVSxFQUFFLHNCQUFzQixFQUFFLGtCQUFrQixDQUFDLEVBQUUsQ0FBQztZQUM1RSxJQUFJLG1CQUFVLENBQUMsT0FBTyxFQUFFLHdCQUF3QixJQUFJLE9BQU8sRUFBRTtnQkFDM0QsUUFBUSxFQUFFOztNQUVaLElBQUEsbUJBQVUsR0FBRTtRQUNWLElBQUk7Ozs7O0NBS1g7YUFDTSxDQUFDLENBQUM7UUFDTCxDQUFDO1FBRUQsSUFBSSxtQkFBVSxDQUFDLE9BQU8sRUFBRSxrQ0FBa0MsWUFBWSxPQUFPLEVBQUU7WUFDN0UsUUFBUSxFQUFFOztNQUVWLElBQUEsbUJBQVUsR0FBRTtRQUNWLFlBQVk7Ozs7Ozs7Ozs7Ozs7Ozs7OztZQWtCUixJQUFJLGNBQWMsV0FBVzs7WUFFN0IsV0FBVzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O3NEQXdLK0Isa0JBQWtCOzs7Ozs7Ozs7Ozs7Ozt1Q0FjakMsT0FBTztrQkFDNUIsSUFBSTsyQ0FDcUIsVUFBVTs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7O0NBaUNwRDtTQUNJLENBQUMsQ0FBQztJQUNMLENBQUM7Q0FDRjtBQXhTRCxzREF3U0MiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyByYW5kb21VVUlEIH0gZnJvbSAnY3J5cHRvJztcbmltcG9ydCB7IFNhbXBsZUZpbGUgfSBmcm9tICdwcm9qZW4nO1xuaW1wb3J0IHsgTm9kZVByb2plY3QgfSBmcm9tICdwcm9qZW4vbGliL2phdmFzY3JpcHQnO1xuXG5leHBvcnQgaW50ZXJmYWNlIFNvZnR3YXJlRmFjdG9yeU1vZGVsc09wdGlvbnMge1xuICAvKipcbiAgICogVGhlIGJhcmUgcmVwbyBuYW1lIChlLmcuIGBleHBlbnNlLXRyYWNrZXJgKSwgdXNlZCB0byBkZXJpdmUgbW9kZWwgbmFtZXNcbiAgICogbGlrZSBgPHJlcG9OYW1lPi1mYWN0b3J5YCwgYDxyZXBvTmFtZT4tc2hlbGxgLCBgPHJlcG9OYW1lPi1zZWNyZXRzYC5cbiAgICovXG4gIHJlYWRvbmx5IHJlcG9OYW1lOiBzdHJpbmc7XG5cbiAgLyoqXG4gICAqIFRoZSBHaXRIdWIgb3JnIHRoZSByZXBvIGxpdmVzIGluLlxuICAgKi9cbiAgcmVhZG9ubHkgZ2l0aHViT3duZXI6IHN0cmluZztcblxuICAvKipcbiAgICogVGhlIGJyYW5jaCB0aGUgZmFjdG9yeSBvcGVucyBwdWxsIHJlcXVlc3RzIGFnYWluc3QuXG4gICAqL1xuICByZWFkb25seSBiYXNlQnJhbmNoOiBzdHJpbmc7XG59XG5cbi8qKlxuICogU2VlZCBzd2FtcCBtb2RlbCBpbnN0YW5jZXMgd2lyaW5nIHRoZSBgQHN3YW1wL3NvZnR3YXJlLWZhY3RvcnlgIHN0YXRlXG4gKiBtYWNoaW5lIHRvIHRoaXMgcmVwbydzIEdpdEh1YiBpc3N1ZXMvUFJzIGFuZCBzaGVsbCBzdGVwcy4gVGhlc2UgYXJlXG4gKiBgU2FtcGxlRmlsZWBzICh3cml0dGVuIG9uY2UsIHRoZW4gb3duZWQgYnkgdGhlIHRlYW0pIHNpbmNlIHRoZSBmYWN0b3J5J3NcbiAqIHN0YWdlcy9nYXRlcyBhcmUgdGhlIGtpbmQgb2YgdGhpbmcgYSB0ZWFtIHR1bmVzIGZvciBpdHMgb3duIHdvcmtmbG93LlxuICovXG5leHBvcnQgY2xhc3MgU29mdHdhcmVGYWN0b3J5TW9kZWxzIHtcbiAgY29uc3RydWN0b3IocHJvamVjdDogTm9kZVByb2plY3QsIG9wdGlvbnM6IFNvZnR3YXJlRmFjdG9yeU1vZGVsc09wdGlvbnMpIHtcbiAgICBjb25zdCB7IHJlcG9OYW1lLCBnaXRodWJPd25lciwgYmFzZUJyYW5jaCB9ID0gb3B0aW9ucztcbiAgICBjb25zdCByZXBvID0gYCR7Z2l0aHViT3duZXJ9LyR7cmVwb05hbWV9YDtcbiAgICBjb25zdCBpc3N1ZXNNb2RlbCA9IGAke3JlcG9OYW1lfS1pc3N1ZXNgO1xuICAgIGNvbnN0IHByTW9kZWwgPSBgJHtyZXBvTmFtZX0tcHJgO1xuICAgIGNvbnN0IHNoZWxsTW9kZWwgPSBgJHtyZXBvTmFtZX0tc2hlbGxgO1xuICAgIGNvbnN0IHNoZWxsQXJjaGl0ZWN0dXJlTW9kZWwgPSBgJHtyZXBvTmFtZX0tc2hlbGwtYXJjaGl0ZWN0dXJlYDtcbiAgICBjb25zdCBzaGVsbE1hbmlmZXN0TW9kZWwgPSBgJHtyZXBvTmFtZX0tc2hlbGwtbWFuaWZlc3RgO1xuICAgIGNvbnN0IHNlY3JldHNWYXVsdCA9IGAke3JlcG9OYW1lfS1zZWNyZXRzYDtcbiAgICBjb25zdCBmYWN0b3J5TW9kZWwgPSBgJHtyZXBvTmFtZX0tZmFjdG9yeWA7XG5cbiAgICBuZXcgU2FtcGxlRmlsZShwcm9qZWN0LCBgbW9kZWxzL0Bnb29kY3JhZnQvZ2l0aHViLyR7cHJNb2RlbH0ueWFtbGAsIHtcbiAgICAgIGNvbnRlbnRzOiBgdHlwZTogJ0Bnb29kY3JhZnQvZ2l0aHViJ1xudHlwZVZlcnNpb246IDIwMjYuMDYuMTQuMVxuaWQ6ICR7cmFuZG9tVVVJRCgpfVxubmFtZTogJHtwck1vZGVsfVxudmVyc2lvbjogMVxudGFnczoge31cbmdsb2JhbEFyZ3VtZW50czpcbiAgdG9rZW46ICdcXCR7eyB2YXVsdC5nZXQoJHtzZWNyZXRzVmF1bHR9LCBHSVRIVUJfVE9LRU4pIH19J1xuICBvd25lcjogJHtnaXRodWJPd25lcn1cbm1ldGhvZHM6IHt9XG5gLFxuICAgIH0pO1xuXG4gICAgbmV3IFNhbXBsZUZpbGUocHJvamVjdCwgYG1vZGVscy9Ad2ViZnJhbXAvZ2l0aHViLyR7aXNzdWVzTW9kZWx9LnlhbWxgLCB7XG4gICAgICBjb250ZW50czogYHR5cGU6ICdAd2ViZnJhbXAvZ2l0aHViJ1xudHlwZVZlcnNpb246IDIwMjYuMDguMjguMVxuaWQ6ICR7cmFuZG9tVVVJRCgpfVxubmFtZTogJHtpc3N1ZXNNb2RlbH1cbnZlcnNpb246IDFcbnRhZ3M6IHt9XG5nbG9iYWxBcmd1bWVudHM6IHt9XG5tZXRob2RzOiB7fVxuYCxcbiAgICB9KTtcblxuICAgIGZvciAoY29uc3QgbmFtZSBvZiBbc2hlbGxNb2RlbCwgc2hlbGxBcmNoaXRlY3R1cmVNb2RlbCwgc2hlbGxNYW5pZmVzdE1vZGVsXSkge1xuICAgICAgbmV3IFNhbXBsZUZpbGUocHJvamVjdCwgYG1vZGVscy9jb21tYW5kL3NoZWxsLyR7bmFtZX0ueWFtbGAsIHtcbiAgICAgICAgY29udGVudHM6IGB0eXBlOiBjb21tYW5kL3NoZWxsXG50eXBlVmVyc2lvbjogMjAyNi4wMi4wOS4xXG5pZDogJHtyYW5kb21VVUlEKCl9XG5uYW1lOiAke25hbWV9XG52ZXJzaW9uOiAxXG50YWdzOiB7fVxuZ2xvYmFsQXJndW1lbnRzOiB7fVxubWV0aG9kczoge31cbmAsXG4gICAgICB9KTtcbiAgICB9XG5cbiAgICBuZXcgU2FtcGxlRmlsZShwcm9qZWN0LCBgbW9kZWxzL0Bzd2FtcC9zb2Z0d2FyZS1mYWN0b3J5LyR7ZmFjdG9yeU1vZGVsfS55YW1sYCwge1xuICAgICAgY29udGVudHM6IGB0eXBlOiAnQHN3YW1wL3NvZnR3YXJlLWZhY3RvcnknXG50eXBlVmVyc2lvbjogMjAyNi4wNi4yNC4xXG5pZDogJHtyYW5kb21VVUlEKCl9XG5uYW1lOiAke2ZhY3RvcnlNb2RlbH1cbnZlcnNpb246IDFcbnRhZ3M6IHt9XG5yZXBvcnRzOlxuICByZXF1aXJlOlxuICAgIC0gbmFtZTogXCJAc3dhbXAvc29mdHdhcmUtZmFjdG9yeS93b3JrLWl0ZW0tc3VtbWFyeVwiXG4gICAgICBtZXRob2RzOiBbc3VtbWFyeV1cbmdsb2JhbEFyZ3VtZW50czpcbiAgc3RhZ2VzOlxuICAgIC0gaWQ6IGludGFrZVxuICAgICAgaW5pdGlhbDogdHJ1ZVxuICAgICAgZGVzY3JpcHRpb246ID4tXG4gICAgICAgIEZldGNoIHRoZSBHaXRIdWIgaXNzdWUgZm9yIHRoaXMgd29yayBpdGVtICh3b3JrSXRlbSBpcyB0aGUgYmFyZVxuICAgICAgICBpc3N1ZSBudW1iZXIpIGFuZCByZWNvcmQgaXRzIGNvbnRleHQuXG4gICAgICB3b3JrOlxuICAgICAgICBtb2RlOiBpbnRlcmFjdGl2ZVxuICAgICAgICBzeXN0ZW1Qcm9tcHQ6ID4tXG4gICAgICAgICAgRmV0Y2ggdGhlIEdpdEh1YiBpc3N1ZSBudW1iZXJlZCBcXCR7eyBzZWxmLndvcmtJdGVtIH19IGZvclxuICAgICAgICAgICR7cmVwb30gdXNpbmcgdGhlICR7aXNzdWVzTW9kZWx9IG1vZGVsXG4gICAgICAgICAgKGxpc3RfaXNzdWVzLCB0aGVuIHN3YW1wIGRhdGEgcXVlcnkgZmlsdGVyZWQgYnkgbnVtYmVyIOKAlCBzZWUgdGhlXG4gICAgICAgICAgJHtpc3N1ZXNNb2RlbH0gZGF0YSksIGFuZCByZWNvcmQgdGhlIGlzc3VlLWNvbnRleHQgYXJ0aWZhY3QuXG4gICAgICBhcnRpZmFjdHM6XG4gICAgICAgIC0gbmFtZTogaXNzdWUtY29udGV4dFxuICAgICAgICAgIHNjaGVtYTpcbiAgICAgICAgICAgIHR5cGU6IG9iamVjdFxuICAgICAgICAgICAgcmVxdWlyZWQ6IFtudW1iZXIsIHRpdGxlLCB1cmxdXG4gICAgICAgICAgICBwcm9wZXJ0aWVzOlxuICAgICAgICAgICAgICBudW1iZXI6IHsgdHlwZTogaW50ZWdlciB9XG4gICAgICAgICAgICAgIHRpdGxlOiB7IHR5cGU6IHN0cmluZywgbWluTGVuZ3RoOiAxIH1cbiAgICAgICAgICAgICAgYm9keTogeyB0eXBlOiBzdHJpbmcgfVxuICAgICAgICAgICAgICB1cmw6IHsgdHlwZTogc3RyaW5nLCBtaW5MZW5ndGg6IDEgfVxuICAgICAgICAgICAgICBsYWJlbHM6IHsgdHlwZTogYXJyYXksIGl0ZW1zOiB7IHR5cGU6IHN0cmluZyB9IH1cbiAgICAgIHRyYW5zaXRpb25zOlxuICAgICAgICAtIG5hbWU6IHN1Ym1pdFxuICAgICAgICAgIHRvOiBwbGFubmluZ1xuICAgICAgICAgIGdhdGVzOlxuICAgICAgICAgICAgLSB0eXBlOiBhcnRpZmFjdC1leGlzdHNcbiAgICAgICAgICAgICAgY29uZmlnOiB7IGFydGlmYWN0OiBpc3N1ZS1jb250ZXh0IH1cblxuICAgIC0gaWQ6IHBsYW5uaW5nXG4gICAgICBkZXNjcmlwdGlvbjogPi1cbiAgICAgICAgVHVybiB0aGUgaXNzdWUgaW50byBhIHJlYWwgT3BlblNwZWMgY2hhbmdlIChwcm9wb3NhbCwgZGVzaWduLFxuICAgICAgICBzcGVjcywgdGFza3MpIOKAlCBPcGVuU3BlYydzIHRhc2sgbGlzdCBpcyB0aGUgcGxhbiwgbm90IGFcbiAgICAgICAgZnJlZWZvcm0gYXJ0aWZhY3QuXG4gICAgICB3b3JrOlxuICAgICAgICBtb2RlOiBpbnRlcmFjdGl2ZVxuICAgICAgICBzeXN0ZW1Qcm9tcHQ6ID4tXG4gICAgICAgICAgVXNpbmcgdGhlIHJlY29yZGVkIGlzc3VlLWNvbnRleHQgYXJ0aWZhY3QsIHVzZSB0aGVcbiAgICAgICAgICBvcGVuc3BlYy1wcm9wb3NlIHNraWxsIHRvIGNyZWF0ZSBhIGNvbXBsZXRlIE9wZW5TcGVjIGNoYW5nZVxuICAgICAgICAgIChwcm9wb3NhbC5tZCwgZGVzaWduLm1kLCBzcGVjcyB3aXRoIGRlbHRhcywgdGFza3MubWQpIGZvciB0aGlzXG4gICAgICAgICAgd29yayBpdGVtIHVuZGVyIG9wZW5zcGVjL2NoYW5nZXMvPGNoYW5nZVNsdWc+LyDigJQgZG8gbm90IHdyaXRlIGFcbiAgICAgICAgICBmcmVlZm9ybSBwbGFuIGluc3RlYWQgb2YgdGhpcy4gVGhlbiByZWNvcmQgdGhlIHBsYW4gYXJ0aWZhY3RcbiAgICAgICAgICB3aXRoIHRoZSBjaGFuZ2UncyBzbHVnIGFuZCBhIG9uZS1saW5lIHN1bW1hcnkuXG4gICAgICBhcnRpZmFjdHM6XG4gICAgICAgIC0gbmFtZTogcGxhblxuICAgICAgICAgIHNjaGVtYTpcbiAgICAgICAgICAgIHR5cGU6IG9iamVjdFxuICAgICAgICAgICAgcmVxdWlyZWQ6IFtjaGFuZ2VTbHVnLCBzdW1tYXJ5XVxuICAgICAgICAgICAgcHJvcGVydGllczpcbiAgICAgICAgICAgICAgY2hhbmdlU2x1ZzogeyB0eXBlOiBzdHJpbmcsIG1pbkxlbmd0aDogMSB9XG4gICAgICAgICAgICAgIHN1bW1hcnk6IHsgdHlwZTogc3RyaW5nLCBtaW5MZW5ndGg6IDEgfVxuICAgICAgdHJhbnNpdGlvbnM6XG4gICAgICAgIC0gbmFtZTogc3VibWl0XG4gICAgICAgICAgdG86IG9wZW5zcGVjLXBsYW4tdmVyaWZ5XG4gICAgICAgICAgZ2F0ZXM6XG4gICAgICAgICAgICAtIHR5cGU6IGFydGlmYWN0LWV4aXN0c1xuICAgICAgICAgICAgICBjb25maWc6IHsgYXJ0aWZhY3Q6IHBsYW4gfVxuXG4gICAgLSBpZDogb3BlbnNwZWMtcGxhbi12ZXJpZnlcbiAgICAgIGRlc2NyaXB0aW9uOiA+LVxuICAgICAgICBWZXJpZnkgdGhlIE9wZW5TcGVjIGNoYW5nZSBpcyByZWFsIGFuZCB3ZWxsLWZvcm1lZCAobm90IGp1c3QgYW5cbiAgICAgICAgYXJ0aWZhY3QgY2xhaW1pbmcgb25lIGV4aXN0cykgYmVmb3JlIGltcGxlbWVudGF0aW9uIHN0YXJ0cy5cbiAgICAgIHdvcms6XG4gICAgICAgIG1vZGU6IHdvcmtmbG93XG4gICAgICAgIHdvcmtmbG93OlxuICAgICAgICAgIG5hbWU6IG9wZW5zcGVjLXBsYW4tdmVyaWZ5XG4gICAgICAgICAgaW5wdXRzOlxuICAgICAgICAgICAgY2hhbmdlU2x1ZzogJ1xcJHt7IGRhdGEubGF0ZXN0KHNlbGYubmFtZSwgXCJhcnRpZmFjdC1wbGFuXCIpLnBheWxvYWQuY2hhbmdlU2x1ZyB9fSdcbiAgICAgICAgcmVzdWx0RXZpZGVuY2U6IHBsYW4tdmVyaWZ5LXJ1blxuICAgICAgdHJhbnNpdGlvbnM6XG4gICAgICAgIC0gbmFtZTogcGFzc1xuICAgICAgICAgIHRvOiBpbXBsZW1lbnRpbmdcbiAgICAgICAgICBnYXRlczpcbiAgICAgICAgICAgIC0gdHlwZTogd29ya2Zsb3ctc3VjY2VlZGVkXG4gICAgICAgICAgICAgIGNvbmZpZzogeyB3b3JrZmxvdzogb3BlbnNwZWMtcGxhbi12ZXJpZnkgfVxuICAgICAgICAtIG5hbWU6IGZhaWxcbiAgICAgICAgICB0bzogcGxhbm5pbmdcbiAgICAgICAgICBnYXRlczpcbiAgICAgICAgICAgIC0gdHlwZTogZXZpZGVuY2UtcmVjb3JkZWRcbiAgICAgICAgICAgICAgY29uZmlnOiB7IG5hbWU6IHBsYW4tdmVyaWZ5LXJ1biwgcmVxdWlyZUZpZWxkOiB7IHN0YXR1czogZmFpbGVkIH0gfVxuXG4gICAgLSBpZDogaW1wbGVtZW50aW5nXG4gICAgICBkZXNjcmlwdGlvbjogSW1wbGVtZW50IHRoZSBPcGVuU3BlYyBjaGFuZ2UncyB0YXNrcyBvbiBhIGJyYW5jaC5cbiAgICAgIHdvcms6XG4gICAgICAgIG1vZGU6IGludGVyYWN0aXZlXG4gICAgICAgIHN5c3RlbVByb21wdDogPi1cbiAgICAgICAgICBVc2UgdGhlIG9wZW5zcGVjLWFwcGx5LWNoYW5nZSBza2lsbCBhZ2FpbnN0IHRoZSBjaGFuZ2UgcmVjb3JkZWRcbiAgICAgICAgICBpbiB0aGUgcGxhbiBhcnRpZmFjdCdzIGNoYW5nZVNsdWcg4oCUIHdvcmsgdGhyb3VnaCBpdHMgdGFza3MubWRcbiAgICAgICAgICBvbmUgdGFzayBhdCBhIHRpbWUsIGNoZWNraW5nIGVhY2ggb2ZmIChcXGAtIFsgXVxcYCAtPiBcXGAtIFt4XVxcYCkgYXNcbiAgICAgICAgICB5b3UgY29tcGxldGUgaXQsIHVudGlsIGV2ZXJ5IHRhc2sgaXMgZG9uZS4gQ29tbWl0IHRoZSB3b3JrIG9uIGFcbiAgICAgICAgICBmZWF0dXJlIGJyYW5jaC4gUmVjb3JkIHRoZSBjaGFuZ2Utc3VtbWFyeSBhcnRpZmFjdCBhbmQgdGhlXG4gICAgICAgICAgY2hhbmdlLXJlcXVlc3QgZXZpZGVuY2UgKGJyYW5jaCBuYW1lIGFuZCBoZWFkIGNvbW1pdCBzaGEg4oCUIG5vXG4gICAgICAgICAgUFIgeWV0LCB0aGF0IGhhcHBlbnMgYWZ0ZXIgcHJlLVBSIHZlcmlmaWNhdGlvbiBwYXNzZXMpLlxuICAgICAgYXJ0aWZhY3RzOlxuICAgICAgICAtIG5hbWU6IGNoYW5nZS1zdW1tYXJ5XG4gICAgICAgICAgc2NoZW1hOlxuICAgICAgICAgICAgdHlwZTogb2JqZWN0XG4gICAgICAgICAgICByZXF1aXJlZDogW3N1bW1hcnksIGhlYWRTaGFdXG4gICAgICAgICAgICBwcm9wZXJ0aWVzOlxuICAgICAgICAgICAgICBzdW1tYXJ5OiB7IHR5cGU6IHN0cmluZywgbWluTGVuZ3RoOiAxIH1cbiAgICAgICAgICAgICAgaGVhZFNoYTogeyB0eXBlOiBzdHJpbmcsIG1pbkxlbmd0aDogNyB9XG4gICAgICBldmlkZW5jZTpcbiAgICAgICAgLSBuYW1lOiBjaGFuZ2UtcmVxdWVzdFxuICAgICAgICAgIHNjaGVtYTpcbiAgICAgICAgICAgIHR5cGU6IG9iamVjdFxuICAgICAgICAgICAgcmVxdWlyZWQ6IFticmFuY2gsIGhlYWRTaGFdXG4gICAgICAgICAgICBwcm9wZXJ0aWVzOlxuICAgICAgICAgICAgICBicmFuY2g6IHsgdHlwZTogc3RyaW5nLCBtaW5MZW5ndGg6IDEgfVxuICAgICAgICAgICAgICBoZWFkU2hhOiB7IHR5cGU6IHN0cmluZywgbWluTGVuZ3RoOiA3IH1cbiAgICAgICAgICAgICAgdXJsOiB7IHR5cGU6IHN0cmluZyB9XG4gICAgICB0cmFuc2l0aW9uczpcbiAgICAgICAgLSBuYW1lOiBzdWJtaXRcbiAgICAgICAgICB0bzogb3BlbnNwZWMtdGFza3MtdmVyaWZ5XG4gICAgICAgICAgZ2F0ZXM6XG4gICAgICAgICAgICAtIHR5cGU6IGFydGlmYWN0LWV4aXN0c1xuICAgICAgICAgICAgICBjb25maWc6IHsgYXJ0aWZhY3Q6IGNoYW5nZS1zdW1tYXJ5IH1cbiAgICAgICAgICAgIC0gdHlwZTogZXZpZGVuY2UtcmVjb3JkZWRcbiAgICAgICAgICAgICAgY29uZmlnOiB7IG5hbWU6IGNoYW5nZS1yZXF1ZXN0IH1cblxuICAgIC0gaWQ6IG9wZW5zcGVjLXRhc2tzLXZlcmlmeVxuICAgICAgZGVzY3JpcHRpb246ID4tXG4gICAgICAgIFZlcmlmeSBldmVyeSB0YXNrIGluIHRoZSBPcGVuU3BlYyBjaGFuZ2UgaXMgYWN0dWFsbHkgY2hlY2tlZCBvZmZcbiAgICAgICAgYmVmb3JlIHByZS1QUiB2ZXJpZmljYXRpb24gcnVucy5cbiAgICAgIHdvcms6XG4gICAgICAgIG1vZGU6IHdvcmtmbG93XG4gICAgICAgIHdvcmtmbG93OlxuICAgICAgICAgIG5hbWU6IG9wZW5zcGVjLXRhc2tzLXZlcmlmeVxuICAgICAgICAgIGlucHV0czpcbiAgICAgICAgICAgIGNoYW5nZVNsdWc6ICdcXCR7eyBkYXRhLmxhdGVzdChzZWxmLm5hbWUsIFwiYXJ0aWZhY3QtcGxhblwiKS5wYXlsb2FkLmNoYW5nZVNsdWcgfX0nXG4gICAgICAgIHJlc3VsdEV2aWRlbmNlOiB0YXNrcy12ZXJpZnktcnVuXG4gICAgICB0cmFuc2l0aW9uczpcbiAgICAgICAgLSBuYW1lOiBwYXNzXG4gICAgICAgICAgdG86IHByZS1wci12ZXJpZnlcbiAgICAgICAgICBnYXRlczpcbiAgICAgICAgICAgIC0gdHlwZTogd29ya2Zsb3ctc3VjY2VlZGVkXG4gICAgICAgICAgICAgIGNvbmZpZzogeyB3b3JrZmxvdzogb3BlbnNwZWMtdGFza3MtdmVyaWZ5IH1cbiAgICAgICAgLSBuYW1lOiBmYWlsXG4gICAgICAgICAgdG86IGltcGxlbWVudGluZ1xuICAgICAgICAgIGdhdGVzOlxuICAgICAgICAgICAgLSB0eXBlOiBldmlkZW5jZS1yZWNvcmRlZFxuICAgICAgICAgICAgICBjb25maWc6IHsgbmFtZTogdGFza3MtdmVyaWZ5LXJ1biwgcmVxdWlyZUZpZWxkOiB7IHN0YXR1czogZmFpbGVkIH0gfVxuXG4gICAgLSBpZDogcHJlLXByLXZlcmlmeVxuICAgICAgZGVzY3JpcHRpb246ID4tXG4gICAgICAgIFByZS1QUiB2ZXJpZmljYXRpb246IGJ1aWxkL3N5bnRoL3Rlc3QvbGludCBwbHVzIHRoZSBhcmNoaXRlY3R1cmVcbiAgICAgICAgcnVsZSByZWdpc3RyeSwgcnVuIGJlZm9yZSB0aGUgUFIgaXMgb3BlbmVkIHJhdGhlciB0aGFuIHJlbHlpbmcgb25cbiAgICAgICAgQ0kgYWZ0ZXIgdGhlIGZhY3QuXG4gICAgICB3b3JrOlxuICAgICAgICBtb2RlOiB3b3JrZmxvd1xuICAgICAgICB3b3JrZmxvdzpcbiAgICAgICAgICBuYW1lOiBwcmUtcHItdmVyaWZ5XG4gICAgICAgICAgaW5wdXRzOlxuICAgICAgICAgICAgY29tbWl0OiAnXFwke3sgZGF0YS5sYXRlc3Qoc2VsZi5uYW1lLCBcImV2aWRlbmNlLWNoYW5nZS1yZXF1ZXN0XCIpLnBheWxvYWQuaGVhZFNoYSB9fSdcbiAgICAgICAgICAgIGJyYW5jaDogJ1xcJHt7IGRhdGEubGF0ZXN0KHNlbGYubmFtZSwgXCJldmlkZW5jZS1jaGFuZ2UtcmVxdWVzdFwiKS5wYXlsb2FkLmJyYW5jaCB9fSdcbiAgICAgICAgcmVzdWx0RXZpZGVuY2U6IHZlcmlmeS1ydW5cbiAgICAgIHRyYW5zaXRpb25zOlxuICAgICAgICAtIG5hbWU6IHBhc3NcbiAgICAgICAgICB0bzogb3Blbi1wclxuICAgICAgICAgIGdhdGVzOlxuICAgICAgICAgICAgLSB0eXBlOiB3b3JrZmxvdy1zdWNjZWVkZWRcbiAgICAgICAgICAgICAgY29uZmlnOiB7IHdvcmtmbG93OiBwcmUtcHItdmVyaWZ5IH1cbiAgICAgICAgLSBuYW1lOiBmYWlsXG4gICAgICAgICAgdG86IGltcGxlbWVudGluZ1xuICAgICAgICAgIGdhdGVzOlxuICAgICAgICAgICAgLSB0eXBlOiBldmlkZW5jZS1yZWNvcmRlZFxuICAgICAgICAgICAgICBjb25maWc6IHsgbmFtZTogdmVyaWZ5LXJ1biwgcmVxdWlyZUZpZWxkOiB7IHN0YXR1czogZmFpbGVkIH0gfVxuXG4gICAgLSBpZDogb3Blbi1wclxuICAgICAgZGVzY3JpcHRpb246IE9wZW4gdGhlIHB1bGwgcmVxdWVzdCBub3cgdGhhdCBwcmUtUFIgdmVyaWZpY2F0aW9uIGhhcyBwYXNzZWQuXG4gICAgICB3b3JrOlxuICAgICAgICBtb2RlOiBpbnRlcmFjdGl2ZVxuICAgICAgICBzeXN0ZW1Qcm9tcHQ6ID4tXG4gICAgICAgICAgQmVmb3JlIG9wZW5pbmcgdGhlIFBSLCBmZXRjaCB0aGUgcmVhbCBwcmUtcHItdmVyaWZ5IGF0dGVzdGF0aW9uXG4gICAgICAgICAgd2l0aCBcXGBzd2FtcCBkYXRhIGdldCAtLXdvcmtmbG93IHByZS1wci12ZXJpZnlcbiAgICAgICAgICByZXBvcnQtc3dhbXAtdmVyaWZpY2F0aW9uLWF0dGVzdGF0aW9uIC0tanNvblxcYCBhbmQgaW5jbHVkZSBpdHNcbiAgICAgICAgICBcXGBjb250ZW50XFxgIHZlcmJhdGltIChhcyBhIG1hcmtkb3duIGJsb2NrLCBlLmcuIHVuZGVyIGEgXCJQcmUtUFJcbiAgICAgICAgICB2ZXJpZmljYXRpb25cIiBoZWFkaW5nKSBpbiB0aGUgUFIgYm9keSDigJQgbmV2ZXIganVzdCBhc3NlcnQgaW5cbiAgICAgICAgICBwcm9zZSB0aGF0IGNoZWNrcyBwYXNzZWQuIEFsc28gZmV0Y2ggdGhpcyBzYW1lIHJ1bidzIG1hbmlmZXN0IHN0ZXBcbiAgICAgICAgICBvdXRwdXQgKFxcYHN3YW1wIGRhdGEgcXVlcnkgJ21vZGVsTmFtZSA9PSBcIiR7c2hlbGxNYW5pZmVzdE1vZGVsfVwiICYmXG4gICAgICAgICAgbmFtZSA9PSBcInJlc3VsdFwiJyAtLXNlbGVjdCBhdHRyaWJ1dGVzLnN0ZG91dCAtLWpzb25cXGAsIHRha2UgdGhlXG4gICAgICAgICAgbGF0ZXN0KSBhbmQgYXBwZW5kIGl0IHRvIHRoZSBQUiBib2R5IGFzXG4gICAgICAgICAgXFxgPCEtLSBhdHRlc3RhdGlvbi1tYW5pZmVzdCB7XCJjb21taXRcIjogXCI8aGVhZFNoYT5cIiwgXCJzdGF0dXNcIjpcbiAgICAgICAgICBcInN1Y2NlZWRlZFwiLCAuLi50aGUgbWFuaWZlc3Qgc3RlcCdzIEpTT04sIHNwcmVhZCBpbi4uLn0gLS0+XFxgIOKAlCBhblxuICAgICAgICAgIEhUTUwgY29tbWVudCBzbyBpdCdzIGludmlzaWJsZSB3aGVuIHJlbmRlcmVkIGJ1dCBzdGlsbCBwcmVzZW50IGluXG4gICAgICAgICAgdGhlIHJhdyBib2R5IHRleHQgdmVyaWZ5LWF0dGVzdGF0aW9uLnltbCByZWFkcyB2aWEgdGhlIEdpdEh1YiBBUEkuXG4gICAgICAgICAgQWxzbyBmZXRjaCB0aGUgcGxhbiBhcnRpZmFjdCdzIGNoYW5nZVNsdWcgYW5kIGluY2x1ZGUgYW4gZXhwbGljaXRcbiAgICAgICAgICBcIk9wZW5TcGVjIGNoYW5nZVwiIGxpbmUgaW4gdGhlIFBSIGJvZHkgbmFtaW5nIGl0IGFuZCBpdHMgcGF0aFxuICAgICAgICAgIChcXGBvcGVuc3BlYy9jaGFuZ2VzLzxjaGFuZ2VTbHVnPi9cXGApIOKAlCBldmVyeSBQUiB0aGUgZmFjdG9yeSBvcGVuc1xuICAgICAgICAgIG11c3QgY2FsbCBvdXQgd2hpY2ggT3BlblNwZWMgY2hhbmdlKHMpIGRyb3ZlIHRoZSBzZXNzaW9uLCBub3RcbiAgICAgICAgICBqdXN0IGltcGx5IGl0LiBJZiBwbGFubmluZyBjeWNsZWQgdGhyb3VnaCBtb3JlIHRoYW4gb25lIGNoYW5nZVxuICAgICAgICAgIChhIHJld29yayBsb29wIHByb2R1Y2VkIGEgbmV3IGNoYW5nZVNsdWcpLCBsaXN0IGV2ZXJ5IGRpc3RpbmN0XG4gICAgICAgICAgY2hhbmdlU2x1ZyByZWNvcmRlZCBhY3Jvc3MgdGhlIHJ1bidzIHBsYW4gYXJ0aWZhY3QgdmVyc2lvbnMsIG5vdFxuICAgICAgICAgIG9ubHkgdGhlIGxhdGVzdC4gVGhlbiBjYWxsICR7cHJNb2RlbH0ncyBvcGVuUHIgbWV0aG9kXG4gICAgICAgICAgKHJlcG89JHtyZXBvfSwgaGVhZD08dGhlIGJyYW5jaCByZWNvcmRlZCBpblxuICAgICAgICAgIGNoYW5nZS1yZXF1ZXN0IGV2aWRlbmNlPiwgYmFzZT0ke2Jhc2VCcmFuY2h9LCBkcnlSdW49ZmFsc2UpLCB3aXRoXG4gICAgICAgICAgdGl0bGUvYm9keSByZWZlcmVuY2luZyB0aGUgaW50YWtlIHN0YWdlJ3MgaXNzdWUgbnVtYmVyLCB0aGVcbiAgICAgICAgICBPcGVuU3BlYyBjaGFuZ2UocyksIHRoZSBhdHRlc3RhdGlvbiBibG9jaywgYW5kIHRoZSBtYW5pZmVzdFxuICAgICAgICAgIGNvbW1lbnQsIHRoZW4gcmVjb3JkIHRoZSBwci1vcGVuZWQgZXZpZGVuY2Ugd2l0aCB0aGUgcmV0dXJuZWQgUFJcbiAgICAgICAgICB1cmwgYW5kIG51bWJlci5cbiAgICAgIGV2aWRlbmNlOlxuICAgICAgICAtIG5hbWU6IHByLW9wZW5lZFxuICAgICAgICAgIHNjaGVtYTpcbiAgICAgICAgICAgIHR5cGU6IG9iamVjdFxuICAgICAgICAgICAgcmVxdWlyZWQ6IFt1cmwsIG51bWJlcl1cbiAgICAgICAgICAgIHByb3BlcnRpZXM6XG4gICAgICAgICAgICAgIHVybDogeyB0eXBlOiBzdHJpbmcsIG1pbkxlbmd0aDogMSB9XG4gICAgICAgICAgICAgIG51bWJlcjogeyB0eXBlOiBpbnRlZ2VyIH1cbiAgICAgIHRyYW5zaXRpb25zOlxuICAgICAgICAtIG5hbWU6IHN1Ym1pdFxuICAgICAgICAgIHRvOiBkb25lXG4gICAgICAgICAgZ2F0ZXM6XG4gICAgICAgICAgICAtIHR5cGU6IGV2aWRlbmNlLXJlY29yZGVkXG4gICAgICAgICAgICAgIGNvbmZpZzogeyBuYW1lOiBwci1vcGVuZWQgfVxuXG4gICAgLSBpZDogZG9uZVxuICAgICAgdGVybWluYWw6IHRydWVcblxuICAgIC0gaWQ6IGFib3J0ZWRcbiAgICAgIHRlcm1pbmFsOiB0cnVlXG5cbiAgZ2xvYmFsVHJhbnNpdGlvbnM6XG4gICAgLSBuYW1lOiBhYm9ydFxuICAgICAgdG86IGFib3J0ZWRcbiAgICAgIGdhdGVzOlxuICAgICAgICAtIHR5cGU6IGh1bWFuLWFwcHJvdmFsXG4gICAgICAgICAgY29uZmlnOiB7IGlkOiBhYm9ydC1jb25maXJtYXRpb24gfVxubWV0aG9kczoge31cbmAsXG4gICAgfSk7XG4gIH1cbn1cbiJdfQ==
@@ -0,0 +1,8 @@
1
+ import { NodeProject } from 'projen/lib/javascript';
2
+ /**
3
+ * Deterministic gate: fails unless every task in the named OpenSpec change's
4
+ * tasks.md is checked off. Fully generic — no per-repo parameterization.
5
+ */
6
+ export declare class SoftwareFactoryOpenSpecGates {
7
+ constructor(project: NodeProject);
8
+ }
@@ -0,0 +1,75 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SoftwareFactoryOpenSpecGates = void 0;
4
+ const projen_1 = require("projen");
5
+ /**
6
+ * Deterministic gate: fails unless every task in the named OpenSpec change's
7
+ * tasks.md is checked off. Fully generic — no per-repo parameterization.
8
+ */
9
+ class SoftwareFactoryOpenSpecGates {
10
+ constructor(project) {
11
+ new projen_1.TextFile(project, 'openspec-gates/check-tasks-complete.ts', {
12
+ marker: true,
13
+ lines: [
14
+ '/**',
15
+ ' * Gate: fails unless every task in the named OpenSpec change\'s tasks.md is',
16
+ ' * checked off. `openspec validate`/`openspec status` don\'t check this (an',
17
+ ' * in-progress change is a normal, valid state) — this is the deterministic',
18
+ ' * check the pre-pr pipeline needs before letting implementation proceed.',
19
+ ' */',
20
+ "import { execFileSync } from 'node:child_process';",
21
+ '',
22
+ 'interface OpenSpecChangeSummary {',
23
+ ' name: string;',
24
+ ' completedTasks: number;',
25
+ ' totalTasks: number;',
26
+ ' status: string;',
27
+ '}',
28
+ '',
29
+ 'function main(): number {',
30
+ ' // pnpm\'s `-- <args>` forwarding (and projen\'s receiveArgs) can pass a',
31
+ ' // literal "--" through as an argv token ahead of the real argument.',
32
+ " const slug = process.argv.slice(2).find((arg) => arg !== '--');",
33
+ ' if (!slug) {',
34
+ " console.error('Usage: check-tasks-complete <change-slug>');",
35
+ ' return 1;',
36
+ ' }',
37
+ '',
38
+ " const raw = execFileSync('openspec', ['list', '--json'], { encoding: 'utf8' });",
39
+ ' const { changes } = JSON.parse(raw) as { changes: OpenSpecChangeSummary[] };',
40
+ ' const change = changes.find((c) => c.name === slug);',
41
+ '',
42
+ ' if (!change) {',
43
+ ' console.error(`OpenSpec change "${slug}" not found (openspec list --json)`);',
44
+ ' return 1;',
45
+ ' }',
46
+ " if (change.status !== 'complete') {",
47
+ ' console.error(',
48
+ ' `OpenSpec change "${slug}" has incomplete tasks: ${change.completedTasks}/${change.totalTasks}`,',
49
+ ' );',
50
+ ' return 1;',
51
+ ' }',
52
+ '',
53
+ ' console.log(`OpenSpec change "${slug}": all ${change.totalTasks} tasks complete`);',
54
+ ' return 0;',
55
+ '}',
56
+ '',
57
+ 'process.exit(main());',
58
+ ],
59
+ });
60
+ new projen_1.JsonFile(project, 'openspec-gates/tsconfig.json', {
61
+ marker: false,
62
+ obj: {
63
+ extends: '../tsconfig.json',
64
+ compilerOptions: {
65
+ noEmit: true,
66
+ rootDir: '.',
67
+ },
68
+ include: ['*.ts'],
69
+ exclude: ['node_modules'],
70
+ },
71
+ });
72
+ }
73
+ }
74
+ exports.SoftwareFactoryOpenSpecGates = SoftwareFactoryOpenSpecGates;
75
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiU29mdHdhcmVGYWN0b3J5T3BlblNwZWNHYXRlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uL3NyYy9Tb2Z0d2FyZUZhY3RvcnlPcGVuU3BlY0dhdGVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7OztBQUFBLG1DQUE0QztBQUc1Qzs7O0dBR0c7QUFDSCxNQUFhLDRCQUE0QjtJQUN2QyxZQUFZLE9BQW9CO1FBQzlCLElBQUksaUJBQVEsQ0FBQyxPQUFPLEVBQUUsd0NBQXdDLEVBQUU7WUFDOUQsTUFBTSxFQUFFLElBQUk7WUFDWixLQUFLLEVBQUU7Z0JBQ0wsS0FBSztnQkFDTCw4RUFBOEU7Z0JBQzlFLDZFQUE2RTtnQkFDN0UsNkVBQTZFO2dCQUM3RSwyRUFBMkU7Z0JBQzNFLEtBQUs7Z0JBQ0wsb0RBQW9EO2dCQUNwRCxFQUFFO2dCQUNGLG1DQUFtQztnQkFDbkMsaUJBQWlCO2dCQUNqQiwyQkFBMkI7Z0JBQzNCLHVCQUF1QjtnQkFDdkIsbUJBQW1CO2dCQUNuQixHQUFHO2dCQUNILEVBQUU7Z0JBQ0YsMkJBQTJCO2dCQUMzQiw0RUFBNEU7Z0JBQzVFLHdFQUF3RTtnQkFDeEUsbUVBQW1FO2dCQUNuRSxnQkFBZ0I7Z0JBQ2hCLGlFQUFpRTtnQkFDakUsZUFBZTtnQkFDZixLQUFLO2dCQUNMLEVBQUU7Z0JBQ0YsbUZBQW1GO2dCQUNuRixnRkFBZ0Y7Z0JBQ2hGLHdEQUF3RDtnQkFDeEQsRUFBRTtnQkFDRixrQkFBa0I7Z0JBQ2xCLGtGQUFrRjtnQkFDbEYsZUFBZTtnQkFDZixLQUFLO2dCQUNMLHVDQUF1QztnQkFDdkMsb0JBQW9CO2dCQUNwQix3R0FBd0c7Z0JBQ3hHLFFBQVE7Z0JBQ1IsZUFBZTtnQkFDZixLQUFLO2dCQUNMLEVBQUU7Z0JBQ0Ysc0ZBQXNGO2dCQUN0RixhQUFhO2dCQUNiLEdBQUc7Z0JBQ0gsRUFBRTtnQkFDRix1QkFBdUI7YUFDeEI7U0FDRixDQUFDLENBQUM7UUFFSCxJQUFJLGlCQUFRLENBQUMsT0FBTyxFQUFFLDhCQUE4QixFQUFFO1lBQ3BELE1BQU0sRUFBRSxLQUFLO1lBQ2IsR0FBRyxFQUFFO2dCQUNILE9BQU8sRUFBRSxrQkFBa0I7Z0JBQzNCLGVBQWUsRUFBRTtvQkFDZixNQUFNLEVBQUUsSUFBSTtvQkFDWixPQUFPLEVBQUUsR0FBRztpQkFDYjtnQkFDRCxPQUFPLEVBQUUsQ0FBQyxNQUFNLENBQUM7Z0JBQ2pCLE9BQU8sRUFBRSxDQUFDLGNBQWMsQ0FBQzthQUMxQjtTQUNGLENBQUMsQ0FBQztJQUNMLENBQUM7Q0FDRjtBQWpFRCxvRUFpRUMiLCJzb3VyY2VzQ29udGVudCI6WyJpbXBvcnQgeyBKc29uRmlsZSwgVGV4dEZpbGUgfSBmcm9tICdwcm9qZW4nO1xuaW1wb3J0IHsgTm9kZVByb2plY3QgfSBmcm9tICdwcm9qZW4vbGliL2phdmFzY3JpcHQnO1xuXG4vKipcbiAqIERldGVybWluaXN0aWMgZ2F0ZTogZmFpbHMgdW5sZXNzIGV2ZXJ5IHRhc2sgaW4gdGhlIG5hbWVkIE9wZW5TcGVjIGNoYW5nZSdzXG4gKiB0YXNrcy5tZCBpcyBjaGVja2VkIG9mZi4gRnVsbHkgZ2VuZXJpYyDigJQgbm8gcGVyLXJlcG8gcGFyYW1ldGVyaXphdGlvbi5cbiAqL1xuZXhwb3J0IGNsYXNzIFNvZnR3YXJlRmFjdG9yeU9wZW5TcGVjR2F0ZXMge1xuICBjb25zdHJ1Y3Rvcihwcm9qZWN0OiBOb2RlUHJvamVjdCkge1xuICAgIG5ldyBUZXh0RmlsZShwcm9qZWN0LCAnb3BlbnNwZWMtZ2F0ZXMvY2hlY2stdGFza3MtY29tcGxldGUudHMnLCB7XG4gICAgICBtYXJrZXI6IHRydWUsXG4gICAgICBsaW5lczogW1xuICAgICAgICAnLyoqJyxcbiAgICAgICAgJyAqIEdhdGU6IGZhaWxzIHVubGVzcyBldmVyeSB0YXNrIGluIHRoZSBuYW1lZCBPcGVuU3BlYyBjaGFuZ2VcXCdzIHRhc2tzLm1kIGlzJyxcbiAgICAgICAgJyAqIGNoZWNrZWQgb2ZmLiBgb3BlbnNwZWMgdmFsaWRhdGVgL2BvcGVuc3BlYyBzdGF0dXNgIGRvblxcJ3QgY2hlY2sgdGhpcyAoYW4nLFxuICAgICAgICAnICogaW4tcHJvZ3Jlc3MgY2hhbmdlIGlzIGEgbm9ybWFsLCB2YWxpZCBzdGF0ZSkg4oCUIHRoaXMgaXMgdGhlIGRldGVybWluaXN0aWMnLFxuICAgICAgICAnICogY2hlY2sgdGhlIHByZS1wciBwaXBlbGluZSBuZWVkcyBiZWZvcmUgbGV0dGluZyBpbXBsZW1lbnRhdGlvbiBwcm9jZWVkLicsXG4gICAgICAgICcgKi8nLFxuICAgICAgICBcImltcG9ydCB7IGV4ZWNGaWxlU3luYyB9IGZyb20gJ25vZGU6Y2hpbGRfcHJvY2Vzcyc7XCIsXG4gICAgICAgICcnLFxuICAgICAgICAnaW50ZXJmYWNlIE9wZW5TcGVjQ2hhbmdlU3VtbWFyeSB7JyxcbiAgICAgICAgJyAgbmFtZTogc3RyaW5nOycsXG4gICAgICAgICcgIGNvbXBsZXRlZFRhc2tzOiBudW1iZXI7JyxcbiAgICAgICAgJyAgdG90YWxUYXNrczogbnVtYmVyOycsXG4gICAgICAgICcgIHN0YXR1czogc3RyaW5nOycsXG4gICAgICAgICd9JyxcbiAgICAgICAgJycsXG4gICAgICAgICdmdW5jdGlvbiBtYWluKCk6IG51bWJlciB7JyxcbiAgICAgICAgJyAgLy8gcG5wbVxcJ3MgYC0tIDxhcmdzPmAgZm9yd2FyZGluZyAoYW5kIHByb2plblxcJ3MgcmVjZWl2ZUFyZ3MpIGNhbiBwYXNzIGEnLFxuICAgICAgICAnICAvLyBsaXRlcmFsIFwiLS1cIiB0aHJvdWdoIGFzIGFuIGFyZ3YgdG9rZW4gYWhlYWQgb2YgdGhlIHJlYWwgYXJndW1lbnQuJyxcbiAgICAgICAgXCIgIGNvbnN0IHNsdWcgPSBwcm9jZXNzLmFyZ3Yuc2xpY2UoMikuZmluZCgoYXJnKSA9PiBhcmcgIT09ICctLScpO1wiLFxuICAgICAgICAnICBpZiAoIXNsdWcpIHsnLFxuICAgICAgICBcIiAgICBjb25zb2xlLmVycm9yKCdVc2FnZTogY2hlY2stdGFza3MtY29tcGxldGUgPGNoYW5nZS1zbHVnPicpO1wiLFxuICAgICAgICAnICAgIHJldHVybiAxOycsXG4gICAgICAgICcgIH0nLFxuICAgICAgICAnJyxcbiAgICAgICAgXCIgIGNvbnN0IHJhdyA9IGV4ZWNGaWxlU3luYygnb3BlbnNwZWMnLCBbJ2xpc3QnLCAnLS1qc29uJ10sIHsgZW5jb2Rpbmc6ICd1dGY4JyB9KTtcIixcbiAgICAgICAgJyAgY29uc3QgeyBjaGFuZ2VzIH0gPSBKU09OLnBhcnNlKHJhdykgYXMgeyBjaGFuZ2VzOiBPcGVuU3BlY0NoYW5nZVN1bW1hcnlbXSB9OycsXG4gICAgICAgICcgIGNvbnN0IGNoYW5nZSA9IGNoYW5nZXMuZmluZCgoYykgPT4gYy5uYW1lID09PSBzbHVnKTsnLFxuICAgICAgICAnJyxcbiAgICAgICAgJyAgaWYgKCFjaGFuZ2UpIHsnLFxuICAgICAgICAnICAgIGNvbnNvbGUuZXJyb3IoYE9wZW5TcGVjIGNoYW5nZSBcIiR7c2x1Z31cIiBub3QgZm91bmQgKG9wZW5zcGVjIGxpc3QgLS1qc29uKWApOycsXG4gICAgICAgICcgICAgcmV0dXJuIDE7JyxcbiAgICAgICAgJyAgfScsXG4gICAgICAgIFwiICBpZiAoY2hhbmdlLnN0YXR1cyAhPT0gJ2NvbXBsZXRlJykge1wiLFxuICAgICAgICAnICAgIGNvbnNvbGUuZXJyb3IoJyxcbiAgICAgICAgJyAgICAgIGBPcGVuU3BlYyBjaGFuZ2UgXCIke3NsdWd9XCIgaGFzIGluY29tcGxldGUgdGFza3M6ICR7Y2hhbmdlLmNvbXBsZXRlZFRhc2tzfS8ke2NoYW5nZS50b3RhbFRhc2tzfWAsJyxcbiAgICAgICAgJyAgICApOycsXG4gICAgICAgICcgICAgcmV0dXJuIDE7JyxcbiAgICAgICAgJyAgfScsXG4gICAgICAgICcnLFxuICAgICAgICAnICBjb25zb2xlLmxvZyhgT3BlblNwZWMgY2hhbmdlIFwiJHtzbHVnfVwiOiBhbGwgJHtjaGFuZ2UudG90YWxUYXNrc30gdGFza3MgY29tcGxldGVgKTsnLFxuICAgICAgICAnICByZXR1cm4gMDsnLFxuICAgICAgICAnfScsXG4gICAgICAgICcnLFxuICAgICAgICAncHJvY2Vzcy5leGl0KG1haW4oKSk7JyxcbiAgICAgIF0sXG4gICAgfSk7XG5cbiAgICBuZXcgSnNvbkZpbGUocHJvamVjdCwgJ29wZW5zcGVjLWdhdGVzL3RzY29uZmlnLmpzb24nLCB7XG4gICAgICBtYXJrZXI6IGZhbHNlLFxuICAgICAgb2JqOiB7XG4gICAgICAgIGV4dGVuZHM6ICcuLi90c2NvbmZpZy5qc29uJyxcbiAgICAgICAgY29tcGlsZXJPcHRpb25zOiB7XG4gICAgICAgICAgbm9FbWl0OiB0cnVlLFxuICAgICAgICAgIHJvb3REaXI6ICcuJyxcbiAgICAgICAgfSxcbiAgICAgICAgaW5jbHVkZTogWycqLnRzJ10sXG4gICAgICAgIGV4Y2x1ZGU6IFsnbm9kZV9tb2R1bGVzJ10sXG4gICAgICB9LFxuICAgIH0pO1xuICB9XG59XG4iXX0=
@@ -0,0 +1,28 @@
1
+ import { NodeProject } from 'projen/lib/javascript';
2
+ import { WorkflowActionVersions } from './WorkflowActionVersions';
3
+ export interface SoftwareFactoryWorkflowsOptions {
4
+ /**
5
+ * The bare repo name, used to derive the `<repoName>-shell[-architecture|-manifest]`
6
+ * model names these workflows dispatch shell commands through.
7
+ */
8
+ readonly repoName: string;
9
+ /**
10
+ * Overrides for the pinned action versions used by `verify-attestation.yml`.
11
+ */
12
+ readonly actionVersions?: WorkflowActionVersions;
13
+ }
14
+ /**
15
+ * The 3 swamp workflows the factory's gate stages run (openspec-plan-verify,
16
+ * openspec-tasks-verify, pre-pr-verify), plus the GitHub Action that
17
+ * re-verifies a PR's embedded attestation at merge time.
18
+ *
19
+ * The swamp workflow YAMLs carry a swamp-assigned `id` that must stay stable
20
+ * across synths (swamp uses it as the workflow's identity), so — like the
21
+ * model instances in `SoftwareFactoryModels` — they're `SampleFile`s, written
22
+ * once and then owned by the team. `verify-attestation.yml` has no such
23
+ * identity constraint and is a plain GitHub Actions workflow, so it's built
24
+ * the same managed way `PrDeployWorkflow` is.
25
+ */
26
+ export declare class SoftwareFactoryWorkflows {
27
+ constructor(project: NodeProject, options: SoftwareFactoryWorkflowsOptions);
28
+ }