@daniellins/power-claude 0.16.3

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.

Potentially problematic release.


This version of @daniellins/power-claude might be problematic. Click here for more details.

Files changed (467) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +121 -0
  3. package/bin/cli.js +124 -0
  4. package/installer/__tests__/doctor-ready-oracles.test.cjs +140 -0
  5. package/installer/__tests__/merge-settings.test.cjs +330 -0
  6. package/installer/adopt.js +161 -0
  7. package/installer/copy.js +203 -0
  8. package/installer/doctor.js +266 -0
  9. package/installer/install.js +155 -0
  10. package/installer/manifest.js +102 -0
  11. package/installer/motor-scripts.js +50 -0
  12. package/installer/motor-scripts.json +39 -0
  13. package/installer/uninstall.js +99 -0
  14. package/installer/update.js +376 -0
  15. package/installer/util.js +113 -0
  16. package/installer/wizard.js +64 -0
  17. package/package.json +26 -0
  18. package/scripts/__fixtures__/anti-path-demo/agents/ap-writer.md +13 -0
  19. package/scripts/__fixtures__/anti-path-demo/templates/write-tmpl.md +7 -0
  20. package/scripts/__fixtures__/anti-path-demo/workflows/wf-ap.smoke.json +10 -0
  21. package/scripts/__fixtures__/anti-path-demo/workflows/wf-ap.yaml +25 -0
  22. package/scripts/__fixtures__/assess-tier/retrofit-leve/pod/agents/content-chief.md +1 -0
  23. package/scripts/__fixtures__/assess-tier/retrofit-leve/pod/workflows/wf-criacao-redteam.yaml +3 -0
  24. package/scripts/__fixtures__/assess-tier/retrofit-leve/pod-spec.json +13 -0
  25. package/scripts/__fixtures__/fidelity/fake-green-average-in-band.yaml +21 -0
  26. package/scripts/__fixtures__/fidelity/fake-green-average.yaml +18 -0
  27. package/scripts/__fixtures__/fidelity/fake-green-denominator.yaml +19 -0
  28. package/scripts/__fixtures__/fidelity/fake-green-missing-evidence.yaml +18 -0
  29. package/scripts/__fixtures__/fidelity/fake-green-threshold.yaml +18 -0
  30. package/scripts/__fixtures__/fidelity/fake-green-weight.yaml +18 -0
  31. package/scripts/__fixtures__/fidelity/golden.yaml +31 -0
  32. package/scripts/__fixtures__/fidelity/trindade-incoherent.yaml +15 -0
  33. package/scripts/__fixtures__/forge-canonical/pod-spec-example.json +37 -0
  34. package/scripts/__fixtures__/migrate-squad/authored/agents/offer-engineer.md +17 -0
  35. package/scripts/__fixtures__/migrate-squad/authored/knowledge/no-source.md +7 -0
  36. package/scripts/__fixtures__/migrate-squad/authored/knowledge/value-stack-method.md +8 -0
  37. package/scripts/__fixtures__/migrate-squad/authored/specs/wf-sample.smoke.json +24 -0
  38. package/scripts/__fixtures__/migrate-squad/authored/specs/wf-sample.spec.json +45 -0
  39. package/scripts/__fixtures__/migrate-squad/legacy-squad/HEADLINE.md +3 -0
  40. package/scripts/__fixtures__/migrate-squad/legacy-squad/README.md +4 -0
  41. package/scripts/__fixtures__/migrate-squad/legacy-squad/agents/hormozi-offer.md +14 -0
  42. package/scripts/__fixtures__/migrate-squad/legacy-squad/checklists/dead-redundant.md +4 -0
  43. package/scripts/__fixtures__/migrate-squad/legacy-squad/checklists/method-guide.md +6 -0
  44. package/scripts/__fixtures__/migrate-squad/legacy-squad/checklists/veto-screen.md +4 -0
  45. package/scripts/__fixtures__/migrate-squad/legacy-squad/config/config.yaml +2 -0
  46. package/scripts/__fixtures__/migrate-squad/legacy-squad/data/case-library.md +3 -0
  47. package/scripts/__fixtures__/migrate-squad/legacy-squad/scripts/validate-mvn.py +16 -0
  48. package/scripts/__fixtures__/migrate-squad/legacy-squad/sops/sop-sample.md +3 -0
  49. package/scripts/__fixtures__/migrate-squad/legacy-squad/squad-io.yaml +14 -0
  50. package/scripts/__fixtures__/migrate-squad/legacy-squad/tasks/gather-context.md +7 -0
  51. package/scripts/__fixtures__/migrate-squad/legacy-squad/templates/output-tmpl.md +5 -0
  52. package/scripts/__fixtures__/migrate-squad/legacy-squad/workflows/wf-sample.yaml +24 -0
  53. package/scripts/__fixtures__/pipeline/pipelines/pipeline-checkpoint.yaml +21 -0
  54. package/scripts/__fixtures__/pipeline/pipelines/pipeline-demo.yaml +22 -0
  55. package/scripts/__fixtures__/pipeline/pods/pipe-a/agents/maker.md +13 -0
  56. package/scripts/__fixtures__/pipeline/pods/pipe-a/memory/learnings.consolidated.md +8 -0
  57. package/scripts/__fixtures__/pipeline/pods/pipe-a/memory/learnings.jsonl +83 -0
  58. package/scripts/__fixtures__/pipeline/pods/pipe-a/templates/make-tmpl.md +8 -0
  59. package/scripts/__fixtures__/pipeline/pods/pipe-a/workflows/wf-a.yaml +16 -0
  60. package/scripts/__fixtures__/pipeline/pods/pipe-b/agents/user.md +13 -0
  61. package/scripts/__fixtures__/pipeline/pods/pipe-b/memory/learnings.consolidated.md +8 -0
  62. package/scripts/__fixtures__/pipeline/pods/pipe-b/memory/learnings.jsonl +14 -0
  63. package/scripts/__fixtures__/pipeline/pods/pipe-b/templates/use-tmpl.md +6 -0
  64. package/scripts/__fixtures__/pipeline/pods/pipe-b/workflows/wf-b.yaml +16 -0
  65. package/scripts/__fixtures__/pod-run/agents/forge-chief.md +13 -0
  66. package/scripts/__fixtures__/pod-run/agents/pod-critic.md +13 -0
  67. package/scripts/__fixtures__/pod-run/templates/build-tmpl.md +8 -0
  68. package/scripts/__fixtures__/pod-run/templates/gate-tmpl.md +6 -0
  69. package/scripts/__fixtures__/pod-run/workflows/wf-min.yaml +43 -0
  70. package/scripts/__fixtures__/pod-run/workflows/wf-skip-modes.yaml +50 -0
  71. package/scripts/__fixtures__/pod-workflow/invalid-checkpoint-all-skippable.yaml +30 -0
  72. package/scripts/__fixtures__/pod-workflow/invalid-checkpoint-criteria-escalar-de-bloco.yaml +40 -0
  73. package/scripts/__fixtures__/pod-workflow/invalid-checkpoint-skip-mode-desconhecido.yaml +30 -0
  74. package/scripts/__fixtures__/pod-workflow/invalid-checkpoint-unknown-key.yaml +29 -0
  75. package/scripts/__fixtures__/pod-workflow/invalid-cycle.yaml +26 -0
  76. package/scripts/__fixtures__/pod-workflow/invalid-fix-loop-backedge.yaml +29 -0
  77. package/scripts/__fixtures__/pod-workflow/invalid-fix-loop-forward.yaml +29 -0
  78. package/scripts/__fixtures__/pod-workflow/invalid-handoff-open.yaml +27 -0
  79. package/scripts/__fixtures__/pod-workflow/invalid-knowledge-shape.yaml +18 -0
  80. package/scripts/__fixtures__/pod-workflow/invalid-missing-agent.yaml +15 -0
  81. package/scripts/__fixtures__/pod-workflow/invalid-missing-outputs.yaml +15 -0
  82. package/scripts/__fixtures__/pod-workflow/invalid-missing-template.yaml +15 -0
  83. package/scripts/__fixtures__/pod-workflow/invalid-missing-veto.yaml +13 -0
  84. package/scripts/__fixtures__/pod-workflow/invalid-template-not-on-disk.yaml +16 -0
  85. package/scripts/__fixtures__/pod-workflow/invalid-veto-prose.yaml +17 -0
  86. package/scripts/__fixtures__/pod-workflow/knowledge/a.md +4 -0
  87. package/scripts/__fixtures__/pod-workflow/knowledge/b.md +4 -0
  88. package/scripts/__fixtures__/pod-workflow/knowledge/c.md +4 -0
  89. package/scripts/__fixtures__/pod-workflow/templates/step-tmpl.md +7 -0
  90. package/scripts/__fixtures__/pod-workflow/valid-checkpoint-skip-modes.yaml +29 -0
  91. package/scripts/__fixtures__/pod-workflow/valid-fix-loop.yaml +30 -0
  92. package/scripts/__fixtures__/pod-workflow/valid-knowledge.yaml +30 -0
  93. package/scripts/__fixtures__/pod-workflow/valid-veto-always-true.yaml +20 -0
  94. package/scripts/__fixtures__/pod-workflow/valid.yaml +27 -0
  95. package/scripts/__fixtures__/smoke-canary/agents/canary-reporter.md +13 -0
  96. package/scripts/__fixtures__/smoke-canary/agents/canary-researcher.md +13 -0
  97. package/scripts/__fixtures__/smoke-canary/agents/canary-synth.md +13 -0
  98. package/scripts/__fixtures__/smoke-canary/memory/learnings.consolidated.md +10 -0
  99. package/scripts/__fixtures__/smoke-canary/memory/learnings.jsonl +448 -0
  100. package/scripts/__fixtures__/smoke-canary/templates/report-tmpl.md +9 -0
  101. package/scripts/__fixtures__/smoke-canary/templates/research-tmpl.md +7 -0
  102. package/scripts/__fixtures__/smoke-canary/templates/synth-tmpl.md +9 -0
  103. package/scripts/__fixtures__/smoke-canary/workflows/wf-canary.smoke.json +11 -0
  104. package/scripts/__fixtures__/smoke-canary/workflows/wf-canary.yaml +53 -0
  105. package/scripts/__fixtures__/source-audit/fake-green-bronze-unsignaled.yaml +10 -0
  106. package/scripts/__fixtures__/source-audit/fake-green-counts.yaml +9 -0
  107. package/scripts/__fixtures__/source-audit/fake-green-missing-evidence.yaml +9 -0
  108. package/scripts/__fixtures__/source-audit/fake-green-unclassified.yaml +10 -0
  109. package/scripts/__fixtures__/source-audit/fake-green-verdict.yaml +10 -0
  110. package/scripts/__fixtures__/source-audit/golden-blocked.yaml +10 -0
  111. package/scripts/__fixtures__/source-audit/golden.yaml +11 -0
  112. package/scripts/__fixtures__/sweep-forms/agents/forms-count.md +13 -0
  113. package/scripts/__fixtures__/sweep-forms/agents/forms-field.md +13 -0
  114. package/scripts/__fixtures__/sweep-forms/agents/forms-fileref.md +13 -0
  115. package/scripts/__fixtures__/sweep-forms/agents/forms-json.md +13 -0
  116. package/scripts/__fixtures__/sweep-forms/agents/forms-section.md +13 -0
  117. package/scripts/__fixtures__/sweep-forms/specs/wf-forms.spec.json +5 -0
  118. package/scripts/__fixtures__/sweep-forms/templates/t_count.md +5 -0
  119. package/scripts/__fixtures__/sweep-forms/templates/t_field.md +3 -0
  120. package/scripts/__fixtures__/sweep-forms/templates/t_fileref.md +3 -0
  121. package/scripts/__fixtures__/sweep-forms/templates/t_json.md +0 -0
  122. package/scripts/__fixtures__/sweep-forms/templates/t_section.md +3 -0
  123. package/scripts/__fixtures__/sweep-forms/workflows/wf-forms.smoke.json +13 -0
  124. package/scripts/__fixtures__/sweep-forms/workflows/wf-forms.yaml +67 -0
  125. package/scripts/__fixtures__/validate-story-acs-corpus/corpus-1-noop-state-json.fixture.md +18 -0
  126. package/scripts/__fixtures__/validate-story-acs-corpus/corpus-2-crash-buildworkflowyaml.fixture.md +26 -0
  127. package/scripts/__fixtures__/validate-story-acs-corpus/corpus-3-nasce-verde-skip-modes.fixture.md +29 -0
  128. package/scripts/__fixtures__/validate-story-acs-corpus/corpus-4-guarda-sem-irmao.fixture.md +17 -0
  129. package/scripts/__fixtures__/validate-story-acs-corpus/corpus-5a-diff-stat-sem-guarda.fixture.md +17 -0
  130. package/scripts/__fixtures__/validate-story-acs-corpus/corpus-5b-test-f-sobre-diretorio.fixture.md +20 -0
  131. package/scripts/__fixtures__/veto-checks/copy-clean.md +4 -0
  132. package/scripts/__fixtures__/veto-checks/copy-dashes.md +4 -0
  133. package/scripts/__fixtures__/veto-checks/n8n-bad.md +18 -0
  134. package/scripts/__fixtures__/veto-checks/n8n-ok.md +22 -0
  135. package/scripts/__fixtures__/veto-checks/page-bad.html +7 -0
  136. package/scripts/__fixtures__/veto-checks/phash/copy.png +0 -0
  137. package/scripts/__fixtures__/veto-checks/phash/distinct.png +0 -0
  138. package/scripts/__fixtures__/veto-checks/phash/distinct2.png +0 -0
  139. package/scripts/__fixtures__/veto-checks/phash/make-fixtures.cjs +203 -0
  140. package/scripts/__fixtures__/veto-checks/phash/ref.png +0 -0
  141. package/scripts/__fixtures__/veto-checks/slide-ok.html +15 -0
  142. package/scripts/__fixtures__/veto-checks/slide-overflow.html +14 -0
  143. package/scripts/__fixtures__/veto-checks/sweep-pod/agents/vc-checker.md +13 -0
  144. package/scripts/__fixtures__/veto-checks/sweep-pod/templates/check-tmpl.md +6 -0
  145. package/scripts/__fixtures__/veto-checks/sweep-pod/workflows/wf-vc.smoke.json +10 -0
  146. package/scripts/__fixtures__/veto-checks/sweep-pod/workflows/wf-vc.yaml +19 -0
  147. package/scripts/__fixtures__/voz-do-dono/README.md +23 -0
  148. package/scripts/__fixtures__/voz-do-dono/dono-escalar.yaml +9 -0
  149. package/scripts/__fixtures__/voz-do-dono/dono-guia.yaml +10 -0
  150. package/scripts/__fixtures__/voz-do-dono/peca-3a-pessoa.md +6 -0
  151. package/scripts/__fixtures__/voz-do-dono/peca-com-lexico-proibido.md +6 -0
  152. package/scripts/__fixtures__/voz-do-dono/peca-limpa-1a-pessoa.md +6 -0
  153. package/scripts/__tests__/derive-ready-transitions.test.cjs +269 -0
  154. package/scripts/__tests__/enforce-cycle-authority.test.cjs +869 -0
  155. package/scripts/__tests__/enforce-git-push-authority.test.cjs +701 -0
  156. package/scripts/__tests__/enforce-ready-oracles.test.cjs +745 -0
  157. package/scripts/__tests__/motor-scripts.test.cjs +339 -0
  158. package/scripts/__tests__/pc-run-pod-counter.test.cjs +58 -0
  159. package/scripts/__tests__/pc-run-pod.test.cjs +578 -0
  160. package/scripts/__tests__/validate-pod-workflow.test.cjs +90 -0
  161. package/scripts/__tests__/validate-story-acs.test.cjs +607 -0
  162. package/scripts/anti-path.test.cjs +163 -0
  163. package/scripts/arena.cjs +514 -0
  164. package/scripts/arena.test.cjs +213 -0
  165. package/scripts/brief-leitura-obrigatoria.test.cjs +364 -0
  166. package/scripts/consolidate-knowledge-runner.test.cjs +249 -0
  167. package/scripts/default-funcional.test.cjs +153 -0
  168. package/scripts/derive-ready-transitions.cjs +325 -0
  169. package/scripts/forge-additive.test.cjs +158 -0
  170. package/scripts/forge-assess-tier.cjs +205 -0
  171. package/scripts/forge-assess-tier.test.cjs +114 -0
  172. package/scripts/forge-canonical.test.cjs +624 -0
  173. package/scripts/forge-generate.cjs +1303 -0
  174. package/scripts/forge-harden-learning.cjs +921 -0
  175. package/scripts/forge-harden-learning.test.cjs +242 -0
  176. package/scripts/forge-ingest-memory.cjs +136 -0
  177. package/scripts/forge-upgrade-pod.cjs +368 -0
  178. package/scripts/forge.test.cjs +609 -0
  179. package/scripts/harden-knowledge.test.cjs +612 -0
  180. package/scripts/harness-g5-lite.cjs +243 -0
  181. package/scripts/harness-g5-lite.test.cjs +134 -0
  182. package/scripts/hn-carousel-contrato-entrada.test.cjs +96 -0
  183. package/scripts/migrate-squad-orchestration.test.cjs +146 -0
  184. package/scripts/migration-manifest-sf3.2.test.cjs +382 -0
  185. package/scripts/migration-manifest-sf3.3.test.cjs +170 -0
  186. package/scripts/migration-manifest-sf5.5.test.cjs +189 -0
  187. package/scripts/migration-manifest.cjs +1146 -0
  188. package/scripts/migration-manifest.test.cjs +186 -0
  189. package/scripts/npm-scope-invocation.test.cjs +194 -0
  190. package/scripts/owner-layer.cjs +442 -0
  191. package/scripts/owner-layer.test.cjs +358 -0
  192. package/scripts/pc-run-pipeline.cjs +289 -0
  193. package/scripts/pc-run-pipeline.test.cjs +256 -0
  194. package/scripts/pc-run-pod-countgte.test.cjs +66 -0
  195. package/scripts/pc-run-pod.cjs +1355 -0
  196. package/scripts/phash-gate.test.cjs +433 -0
  197. package/scripts/pod-export-compile.cjs +487 -0
  198. package/scripts/pod-export-compile.test.cjs +312 -0
  199. package/scripts/pod-export-executor.cjs +678 -0
  200. package/scripts/pod-export-executor.test.cjs +1341 -0
  201. package/scripts/pod-export-hardening.test.cjs +531 -0
  202. package/scripts/pod-export-postprocess.cjs +427 -0
  203. package/scripts/pod-export-postprocess.test.cjs +558 -0
  204. package/scripts/pod-export-queue-race.test.cjs +865 -0
  205. package/scripts/pod-export-release.cjs +473 -0
  206. package/scripts/pod-export-release.test.cjs +267 -0
  207. package/scripts/pod-export-service.cjs +1297 -0
  208. package/scripts/pod-export-service.test.cjs +489 -0
  209. package/scripts/pod-export-tools.cjs +470 -0
  210. package/scripts/pod-export-ui.cjs +805 -0
  211. package/scripts/pod-export-ui.test.cjs +301 -0
  212. package/scripts/pod-memory-budget.test.cjs +309 -0
  213. package/scripts/pod-memory-note.cjs +357 -0
  214. package/scripts/pod-memory-note.test.cjs +452 -0
  215. package/scripts/pod-memory.cjs +1252 -0
  216. package/scripts/pod-memory.test.cjs +951 -0
  217. package/scripts/render-png.test.cjs +307 -0
  218. package/scripts/smoke-isolation.test.cjs +100 -0
  219. package/scripts/smoke-pod-workflow.cjs +755 -0
  220. package/scripts/smoke-sweep.test.cjs +170 -0
  221. package/scripts/validate-adopt.js +235 -0
  222. package/scripts/validate-branding.js +84 -0
  223. package/scripts/validate-budgets.js +140 -0
  224. package/scripts/validate-fidelity-output.cjs +267 -0
  225. package/scripts/validate-fidelity-output.test.cjs +101 -0
  226. package/scripts/validate-install.js +230 -0
  227. package/scripts/validate-no-clone.cjs +220 -0
  228. package/scripts/validate-no-clone.test.cjs +77 -0
  229. package/scripts/validate-pipeline.cjs +145 -0
  230. package/scripts/validate-pod-workflow.cjs +878 -0
  231. package/scripts/validate-source-audit.cjs +170 -0
  232. package/scripts/validate-source-audit.test.cjs +90 -0
  233. package/scripts/validate-story-acs.cjs +862 -0
  234. package/scripts/validate-update-scope-motor.js +421 -0
  235. package/scripts/veto-checks/html-valid.cjs +92 -0
  236. package/scripts/veto-checks/lexical-check.cjs +62 -0
  237. package/scripts/veto-checks/phash-gate.cjs +589 -0
  238. package/scripts/veto-checks/render-gate.cjs +165 -0
  239. package/scripts/veto-checks/render-png.cjs +358 -0
  240. package/scripts/veto-checks.test.cjs +219 -0
  241. package/scripts/watcher-audit.cjs +178 -0
  242. package/template/.claude/CLAUDE.md +103 -0
  243. package/template/.claude/agents/pc-analyst.md +100 -0
  244. package/template/.claude/agents/pc-architect.md +32 -0
  245. package/template/.claude/agents/pc-dev.md +27 -0
  246. package/template/.claude/agents/pc-devops.md +45 -0
  247. package/template/.claude/agents/pc-planner.md +32 -0
  248. package/template/.claude/agents/pc-po.md +25 -0
  249. package/template/.claude/agents/pc-qa.md +25 -0
  250. package/template/.claude/commands/power/academy.md +9 -0
  251. package/template/.claude/commands/power/automation.md +9 -0
  252. package/template/.claude/commands/power/content-carousel.md +267 -0
  253. package/template/.claude/commands/power/content.md +9 -0
  254. package/template/.claude/commands/power/dev.md +9 -0
  255. package/template/.claude/commands/power/forge.md +224 -0
  256. package/template/.claude/commands/power/hormozi.md +221 -0
  257. package/template/.claude/commands/power/modo.md +57 -0
  258. package/template/.claude/hooks/README.md +114 -0
  259. package/template/.claude/hooks/context-budget.cjs +64 -0
  260. package/template/.claude/hooks/enforce-cycle-authority.cjs +457 -0
  261. package/template/.claude/hooks/enforce-git-push-authority.cjs +925 -0
  262. package/template/.claude/hooks/enforce-ready-oracles.cjs +502 -0
  263. package/template/.claude/rules/authority.md +76 -0
  264. package/template/.claude/rules/curadoria-de-conhecimento.md +30 -0
  265. package/template/.claude/rules/model-matrix.md +34 -0
  266. package/template/.claude/rules/orchestration.md +133 -0
  267. package/template/.claude/settings.json +50 -0
  268. package/template/.claude/skills/pc-apply-qa-fixes/SKILL.md +108 -0
  269. package/template/.claude/skills/pc-apply-qa-fixes/references/fix-loop.md +40 -0
  270. package/template/.claude/skills/pc-apply-qa-fixes/references/fix-request-template.md +70 -0
  271. package/template/.claude/skills/pc-arena/SKILL.md +96 -0
  272. package/template/.claude/skills/pc-close-story/SKILL.md +105 -0
  273. package/template/.claude/skills/pc-commit/SKILL.md +108 -0
  274. package/template/.claude/skills/pc-develop-story/SKILL.md +116 -0
  275. package/template/.claude/skills/pc-full-cycle/SKILL.md +150 -0
  276. package/template/.claude/skills/pc-pipeline/SKILL.md +63 -0
  277. package/template/.claude/skills/pc-pipeline/references/gate-templates.md +46 -0
  278. package/template/.claude/skills/pc-review-story/SKILL.md +129 -0
  279. package/template/.claude/skills/pc-review-story/references/false-positive-detection.md +50 -0
  280. package/template/.claude/skills/pc-review-story/references/nfr-assess.md +47 -0
  281. package/template/.claude/skills/pc-review-story/references/risk-profile.md +75 -0
  282. package/template/.claude/skills/pc-review-story/references/security-checklist.md +69 -0
  283. package/template/.claude/skills/pc-review-story/references/test-design.md +37 -0
  284. package/template/.claude/skills/pc-review-story/references/trace-requirements.md +46 -0
  285. package/template/.claude/skills/pc-run-pod/SKILL.md +123 -0
  286. package/template/.claude/skills/pc-validate-story/SKILL.md +114 -0
  287. package/template/.claude/skills/pc-wave/SKILL.md +133 -0
  288. package/template/.env.example +10 -0
  289. package/template/knowledge/analyst/brainstorming.md +40 -0
  290. package/template/knowledge/analyst/brief-skeleton.md +32 -0
  291. package/template/knowledge/analyst/competitor-skeleton.md +39 -0
  292. package/template/knowledge/analyst/elicitation-menu.md +49 -0
  293. package/template/knowledge/analyst/market-research-skeleton.md +36 -0
  294. package/template/knowledge/analyst/research-prompt-structure.md +48 -0
  295. package/template/knowledge/analyst/roi-skeleton.md +51 -0
  296. package/template/knowledge/voice-dna/README.md +99 -0
  297. package/template/knowledge/voice-dna/voice-dna.example.yaml +157 -0
  298. package/template/pods/README.md +64 -0
  299. package/template/pods/academy/README.md +37 -0
  300. package/template/pods/academy/agents/academy-chief.md +27 -0
  301. package/template/pods/academy/examples/palestra.example.md +142 -0
  302. package/template/pods/academy/pod-spec.json +44 -0
  303. package/template/pods/academy/templates/materiais-tmpl.md +17 -0
  304. package/template/pods/academy/templates/revisao-didatica-tmpl.md +20 -0
  305. package/template/pods/academy/workflows/wf-materiais-revisao.yaml +30 -0
  306. package/template/pods/authority-content/README.md +43 -0
  307. package/template/pods/authority-content/agents/content-chief.md +28 -0
  308. package/template/pods/authority-content/pod-spec.json +45 -0
  309. package/template/pods/authority-content/templates/criacao-na-voz-tmpl.md +17 -0
  310. package/template/pods/authority-content/templates/red-team-factual-tmpl.md +23 -0
  311. package/template/pods/authority-content/workflows/wf-criacao-redteam.yaml +30 -0
  312. package/template/pods/automation/README.md +37 -0
  313. package/template/pods/automation/agents/langgraph-architect.md +30 -0
  314. package/template/pods/automation/agents/n8n-architect.md +30 -0
  315. package/template/pods/automation/examples/README.md +46 -0
  316. package/template/pods/automation/examples/n8n-content-radar.example.json +380 -0
  317. package/template/pods/automation/knowledge/langgraph-patterns.md +126 -0
  318. package/template/pods/automation/knowledge/n8n-patterns.md +131 -0
  319. package/template/pods/automation/memory/README.md +16 -0
  320. package/template/pods/automation/memory/learnings.consolidated.md +8 -0
  321. package/template/pods/automation/memory/learnings.jsonl +0 -0
  322. package/template/pods/automation/pod-spec.json +109 -0
  323. package/template/pods/automation/specs/wf-langgraph-build.spec.json +107 -0
  324. package/template/pods/automation/squad-io.yaml +28 -0
  325. package/template/pods/automation/templates/lg-build-tmpl.md +17 -0
  326. package/template/pods/automation/templates/lg-design-tmpl.md +23 -0
  327. package/template/pods/automation/templates/lg-discover-tmpl.md +20 -0
  328. package/template/pods/automation/templates/lg-handoff-tmpl.md +20 -0
  329. package/template/pods/automation/templates/lg-test-tmpl.md +20 -0
  330. package/template/pods/automation/templates/n8n-build-tmpl.md +14 -0
  331. package/template/pods/automation/templates/n8n-design-tmpl.md +23 -0
  332. package/template/pods/automation/templates/n8n-discover-tmpl.md +20 -0
  333. package/template/pods/automation/templates/n8n-handoff-tmpl.md +20 -0
  334. package/template/pods/automation/templates/n8n-test-tmpl.md +20 -0
  335. package/template/pods/automation/workflows/wf-langgraph-build.smoke.json +11 -0
  336. package/template/pods/automation/workflows/wf-langgraph-build.yaml +64 -0
  337. package/template/pods/automation/workflows/wf-n8n-build.smoke.json +11 -0
  338. package/template/pods/automation/workflows/wf-n8n-build.yaml +65 -0
  339. package/template/pods/content-carousel/README.md +91 -0
  340. package/template/pods/content-carousel/agents/carousel-builder.md +28 -0
  341. package/template/pods/content-carousel/agents/carousel-visual.md +24 -0
  342. package/template/pods/content-carousel/agents/hn-curator.md +74 -0
  343. package/template/pods/content-carousel/config/heuristic-watcher-rubric.yaml +59 -0
  344. package/template/pods/content-carousel/knowledge/aquisicao-ancora.md +443 -0
  345. package/template/pods/content-carousel/knowledge/cta-brief.md +25 -0
  346. package/template/pods/content-carousel/knowledge/dono.example.yaml +45 -0
  347. package/template/pods/content-carousel/knowledge/identidade-visual-mascote.md +62 -0
  348. package/template/pods/content-carousel/knowledge/image-generation.md +62 -0
  349. package/template/pods/content-carousel/knowledge/selecao-de-ancora.md +60 -0
  350. package/template/pods/content-carousel/knowledge/slides-html.md +56 -0
  351. package/template/pods/content-carousel/memory/README.md +18 -0
  352. package/template/pods/content-carousel/memory/learnings.consolidated.md +6 -0
  353. package/template/pods/content-carousel/memory/learnings.jsonl +0 -0
  354. package/template/pods/content-carousel/pod-spec.json +100 -0
  355. package/template/pods/content-carousel/squad-io.yaml +10 -0
  356. package/template/pods/content-carousel/templates/assembly-tmpl.md +23 -0
  357. package/template/pods/content-carousel/templates/digest-tmpl.md +29 -0
  358. package/template/pods/content-carousel/templates/visuals-tmpl.md +20 -0
  359. package/template/pods/content-carousel/workflows/wf-content-carousel.smoke.json +23 -0
  360. package/template/pods/content-carousel/workflows/wf-content-carousel.yaml +80 -0
  361. package/template/pods/dev-ia/README.md +45 -0
  362. package/template/pods/dev-ia/agents/db-architect.md +33 -0
  363. package/template/pods/dev-ia/agents/ux-architect.md +29 -0
  364. package/template/pods/dev-ia/knowledge/db/migration-protocol.md +95 -0
  365. package/template/pods/dev-ia/knowledge/db/rls-patterns.md +101 -0
  366. package/template/pods/dev-ia/knowledge/db/schema-review-checklist.md +76 -0
  367. package/template/pods/dev-ia/memory/README.md +16 -0
  368. package/template/pods/dev-ia/memory/learnings.consolidated.md +8 -0
  369. package/template/pods/dev-ia/memory/learnings.jsonl +0 -0
  370. package/template/pods/dev-ia/pod-spec.json +34 -0
  371. package/template/pods/dev-ia/templates/schema-review-tmpl.md +23 -0
  372. package/template/pods/dev-ia/templates/ux-flow-review-tmpl.md +23 -0
  373. package/template/pods/dev-ia/tools/db-ops/backup-manager.sh +321 -0
  374. package/template/pods/dev-ia/tools/db-ops/best-practices-auditor.sh +534 -0
  375. package/template/pods/dev-ia/tools/db-ops/bootstrap-runner.sh +255 -0
  376. package/template/pods/dev-ia/tools/db-ops/data-loader.sh +616 -0
  377. package/template/pods/dev-ia/tools/db-ops/detect-database.sh +113 -0
  378. package/template/pods/dev-ia/tools/db-ops/explain-analyzer.sh +285 -0
  379. package/template/pods/dev-ia/tools/db-ops/health-checker.sh +323 -0
  380. package/template/pods/dev-ia/tools/db-ops/migration-runner.sh +308 -0
  381. package/template/pods/dev-ia/tools/db-ops/postgresql-schema-loader.sh +334 -0
  382. package/template/pods/dev-ia/tools/db-ops/query-runner.sh +171 -0
  383. package/template/pods/dev-ia/tools/db-ops/rls-policy-installer.sh +480 -0
  384. package/template/pods/dev-ia/tools/db-ops/rls-test-suite.sh +172 -0
  385. package/template/pods/dev-ia/tools/db-ops/schema-context-loader.sh +176 -0
  386. package/template/pods/hormozi-method/README.md +52 -0
  387. package/template/pods/hormozi-method/agents/acquisition-strategist.md +33 -0
  388. package/template/pods/hormozi-method/agents/conversion-copywriter.md +34 -0
  389. package/template/pods/hormozi-method/agents/monetization-architect.md +35 -0
  390. package/template/pods/hormozi-method/agents/offer-engineer.md +37 -0
  391. package/template/pods/hormozi-method/knowledge/core-four-leads.md +51 -0
  392. package/template/pods/hormozi-method/knowledge/grand-slam-offer.md +66 -0
  393. package/template/pods/hormozi-method/knowledge/growth-decision.md +38 -0
  394. package/template/pods/hormozi-method/knowledge/hooks-and-ads.md +56 -0
  395. package/template/pods/hormozi-method/knowledge/launch-email.md +31 -0
  396. package/template/pods/hormozi-method/knowledge/money-model.md +46 -0
  397. package/template/pods/hormozi-method/knowledge/opportunity-screening.md +44 -0
  398. package/template/pods/hormozi-method/knowledge/pages.md +25 -0
  399. package/template/pods/hormozi-method/knowledge/pos-lancamento-leads.md +56 -0
  400. package/template/pods/hormozi-method/knowledge/pricing.md +36 -0
  401. package/template/pods/hormozi-method/knowledge/retention-ltv.md +37 -0
  402. package/template/pods/hormozi-method/knowledge/scale-roadmap.md +35 -0
  403. package/template/pods/hormozi-method/knowledge/swipefiles-curados.md +44 -0
  404. package/template/pods/hormozi-method/knowledge/value-equation-diagnostic.md +77 -0
  405. package/template/pods/hormozi-method/memory/README.md +16 -0
  406. package/template/pods/hormozi-method/memory/learnings.consolidated.md +6 -0
  407. package/template/pods/hormozi-method/memory/learnings.jsonl +0 -0
  408. package/template/pods/hormozi-method/pod-spec.json +50 -0
  409. package/template/pods/hormozi-method/specs/wf-full-launch-sequence.spec.json +149 -0
  410. package/template/pods/hormozi-method/specs/wf-lead-magnet-pipeline.spec.json +125 -0
  411. package/template/pods/hormozi-method/specs/wf-money-model-design.spec.json +117 -0
  412. package/template/pods/hormozi-method/specs/wf-paid-ads-campaign.spec.json +132 -0
  413. package/template/pods/hormozi-method/specs/wf-scaling-diagnostic.spec.json +121 -0
  414. package/template/pods/hormozi-method/squad-io.yaml +43 -0
  415. package/template/pods/hormozi-method/templates/conversion-copywriter-tmpl.md +20 -0
  416. package/template/pods/hormozi-method/templates/launch-execucao-tmpl.md +17 -0
  417. package/template/pods/hormozi-method/templates/launch-fechamento-tmpl.md +17 -0
  418. package/template/pods/hormozi-method/templates/launch-pos-tmpl.md +17 -0
  419. package/template/pods/hormozi-method/templates/launch-preparacao-tmpl.md +17 -0
  420. package/template/pods/hormozi-method/templates/lm-copy-tmpl.md +17 -0
  421. package/template/pods/hormozi-method/templates/lm-distribuicao-tmpl.md +18 -0
  422. package/template/pods/hormozi-method/templates/lm-estrategia-tmpl.md +17 -0
  423. package/template/pods/hormozi-method/templates/mm-arquitetura-tmpl.md +18 -0
  424. package/template/pods/hormozi-method/templates/mm-copy-tmpl.md +17 -0
  425. package/template/pods/hormozi-method/templates/mm-diagnostico-tmpl.md +14 -0
  426. package/template/pods/hormozi-method/templates/monetization-architect-tmpl.md +17 -0
  427. package/template/pods/hormozi-method/templates/offer-engineer-tmpl.md +20 -0
  428. package/template/pods/hormozi-method/templates/paid-criativos-tmpl.md +19 -0
  429. package/template/pods/hormozi-method/templates/paid-fundacao-tmpl.md +17 -0
  430. package/template/pods/hormozi-method/templates/paid-teste-escala-tmpl.md +17 -0
  431. package/template/pods/hormozi-method/templates/scaling-diagnostico-tmpl.md +17 -0
  432. package/template/pods/hormozi-method/templates/scaling-priorizacao-tmpl.md +17 -0
  433. package/template/pods/hormozi-method/templates/scaling-roadmap-tmpl.md +17 -0
  434. package/template/pods/hormozi-method/workflows/wf-full-launch-sequence.smoke.json +12 -0
  435. package/template/pods/hormozi-method/workflows/wf-full-launch-sequence.yaml +56 -0
  436. package/template/pods/hormozi-method/workflows/wf-grand-slam-offer.smoke.json +11 -0
  437. package/template/pods/hormozi-method/workflows/wf-grand-slam-offer.yaml +45 -0
  438. package/template/pods/hormozi-method/workflows/wf-lead-magnet-pipeline.smoke.json +9 -0
  439. package/template/pods/hormozi-method/workflows/wf-lead-magnet-pipeline.yaml +45 -0
  440. package/template/pods/hormozi-method/workflows/wf-money-model-design.smoke.json +11 -0
  441. package/template/pods/hormozi-method/workflows/wf-money-model-design.yaml +45 -0
  442. package/template/pods/hormozi-method/workflows/wf-paid-ads-campaign.smoke.json +9 -0
  443. package/template/pods/hormozi-method/workflows/wf-paid-ads-campaign.yaml +46 -0
  444. package/template/pods/hormozi-method/workflows/wf-scaling-diagnostic.smoke.json +11 -0
  445. package/template/pods/hormozi-method/workflows/wf-scaling-diagnostic.yaml +44 -0
  446. package/template/pods/squad-forge/README.md +53 -0
  447. package/template/pods/squad-forge/agents/forge-chief.md +31 -0
  448. package/template/pods/squad-forge/agents/mind-extractor.md +29 -0
  449. package/template/pods/squad-forge/agents/pod-critic.md +26 -0
  450. package/template/pods/squad-forge/config/heuristic-watcher-rubric.yaml +74 -0
  451. package/template/pods/squad-forge/knowledge/fidelity-rubric.yaml +176 -0
  452. package/template/pods/squad-forge/knowledge/g6-memory-check.md +17 -0
  453. package/template/pods/squad-forge/knowledge/memory-ingest.md +16 -0
  454. package/template/pods/squad-forge/knowledge/source-audit-rubric.yaml +60 -0
  455. package/template/pods/squad-forge/memory/README.md +16 -0
  456. package/template/pods/squad-forge/memory/learnings.consolidated.md +8 -0
  457. package/template/pods/squad-forge/memory/learnings.jsonl +0 -0
  458. package/template/pods/squad-forge/pod-spec.json +166 -0
  459. package/template/pods/squad-forge/squad-io.yaml +13 -0
  460. package/template/pods/squad-forge/tasks/pipeline.md +158 -0
  461. package/template/pods/squad-forge/templates/discovery-tmpl.md +40 -0
  462. package/template/pods/squad-forge/templates/generation-tmpl.md +18 -0
  463. package/template/pods/squad-forge/templates/red-team-tmpl.md +17 -0
  464. package/template/pods/squad-forge/templates/register-tmpl.md +17 -0
  465. package/template/pods/squad-forge/templates/workflow-tmpl.yaml +101 -0
  466. package/template/pods/squad-forge/workflows/wf-forge.smoke.json +12 -0
  467. package/template/pods/squad-forge/workflows/wf-forge.yaml +60 -0
@@ -0,0 +1,531 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ /**
4
+ * pod-export-hardening.test.cjs — Story SF10.9 (F5): hardening acumulado do `pod-export`.
5
+ *
6
+ * HERMÉTICO (sem VPS/rede externa/spawn real). Cada um dos 5 itens tem PAR de testes:
7
+ * (adversarial) força o estado degradado/malformado/inválido — FALHA sem o fix;
8
+ * (não-regressão) prova que o caminho feliz responde exatamente como antes.
9
+ *
10
+ * AC1 — preflight assere `blessedVetoRels().size > 0`: manifesto (installer/motor-scripts.js)
11
+ * ausente/ilegível/sem entradas de veto-checks FALHA ALTO no arranque (antes o Guard B do
12
+ * motor apenas degradava MUDO — `blessed.size > 0 && ...` PULA a checagem com Set vazio).
13
+ * AC2 — `provision-pod-export.sh` passo 3 exige `installer/motor-scripts.js` (o wrapper carregado
14
+ * em RUNTIME pelo motor), não só o `.json`.
15
+ * AC3 — `confineToRunDir` confina por REALPATH (padrão do Guard B de command_check): symlink
16
+ * DENTRO do runDir apontando para fora é RECUSADO.
17
+ * AC4 — `decodeURIComponent` sob try/catch nos 2 call-sites: `%zz` vira 400, nunca `URIError`
18
+ * não capturado (o processo continua vivo).
19
+ * AC5 — `approveRun` valida `safeRunId` ANTES de qualquer efeito (antes de loadIdentities/
20
+ * authorizeApproval e antes de buildApproveArgs/spawn): runId inválido → recusa SEM spawn.
21
+ *
22
+ * SF10.15 (F5) — fecha o `FIND-1` do gate de SF10.9 (`docs/qa/gates/sf10.9.yaml`): `safeRunId`
23
+ * aceitava `-` na PRIMEIRA posição, então runIds em forma de FLAG chegavam ao argv do executor.
24
+ * Bateria própria abaixo (`sf1015FlagShapedRunId`): os 6 vetores do finding, unidade + rota HTTP
25
+ * REAL (`POST /approve` com segredo de aprovador VÁLIDO → recusa com spawn=0) + não-regressão.
26
+ */
27
+
28
+ const http = require('node:http');
29
+ const fs = require('node:fs');
30
+ const os = require('node:os');
31
+ const path = require('node:path');
32
+ const { EventEmitter } = require('node:events');
33
+
34
+ const svc = require('./pod-export-service.cjs');
35
+ const ui = require('./pod-export-ui.cjs');
36
+ const motor = require('./pc-run-pod.cjs');
37
+
38
+ const REPO_ROOT = path.resolve(__dirname, '..');
39
+ const VALID_HTML = path.join(REPO_ROOT, 'scripts', '__fixtures__', 'veto-checks', 'slide-ok.html');
40
+ const PROVISION_SH = path.join(REPO_ROOT, 'deploy', 'pod-export', 'provision-pod-export.sh');
41
+
42
+ let failures = 0;
43
+ function assert(ok, msg) { if (ok) console.log(`[OK ] ${msg}`); else { console.error(`[FALHA] ${msg}`); failures++; } }
44
+ function throwsFail(fn) { try { fn(); return false; } catch { return true; } }
45
+ function tmpDir(label) { return fs.mkdtempSync(path.join(os.tmpdir(), `sf10.9-${label}-`)); }
46
+ function messageOf(fn) { try { fn(); return ''; } catch (e) { return e.message; } }
47
+
48
+ // ===========================================================================
49
+ // AC1 — preflight fail-loud quando o manifesto blessed está indisponível/vazio.
50
+ // ===========================================================================
51
+ function ac1PreflightBlessed() {
52
+ console.log('\n--- AC1: preflight assere blessedVetoRels().size > 0 (manifesto indisponível → fail-loud) ---');
53
+
54
+ // (não-regressão) manifesto REAL do checkout: carrega e traz os veto-checks blessed (ADR-007).
55
+ const real = svc.blessedVetoRels();
56
+ assert(real.size > 0 && real.has('scripts/veto-checks/html-valid.cjs'),
57
+ `manifesto REAL carrega o conjunto blessed (${real.size} entradas, inclui html-valid.cjs)`);
58
+
59
+ // (não-regressão) fronteira completa com manifesto real → PASSA e reporta blessedCount.
60
+ const s = svc.assertCommandCheckFrontier({ validHtmlPath: VALID_HTML });
61
+ assert(s.blessedCount === real.size && s.rejectedNonBlessed && s.rejectedTraversal && s.acceptedBlessed,
62
+ 'caminho feliz: fronteira command_check íntegra (blessedCount>0, não-blessed e traversal recusados, blessed aceito)');
63
+
64
+ // (adversarial) manifesto AUSENTE em disco: a carga LANÇA aqui (o motor a faz em catch silencioso).
65
+ const noManifest = path.join(tmpDir('no-manifest'), 'motor-scripts.js');
66
+ assert(throwsFail(() => svc.blessedVetoRels({ motorScriptsPath: noManifest })),
67
+ 'manifesto ausente em disco → blessedVetoRels LANÇA (nunca Set vazio silencioso)');
68
+
69
+ // (adversarial) fronteira com manifesto ausente → FAIL-LOUD (antes: passava, Guard B degradava mudo).
70
+ let msg = messageOf(() => svc.assertCommandCheckFrontier({ validHtmlPath: VALID_HTML, motorScriptsPath: noManifest }));
71
+ assert(/FAIL-LOUD/.test(msg) && /manifesto de scripts blessed INDISPON/i.test(msg),
72
+ 'manifesto INDISPONÍVEL → assertCommandCheckFrontier RECUSA (fail-loud, cita o manifesto)');
73
+
74
+ // (adversarial) manifesto ilegível (erro de leitura/parse) → mesma recusa.
75
+ msg = messageOf(() => svc.assertCommandCheckFrontier({
76
+ validHtmlPath: VALID_HTML,
77
+ loadMotorScripts: () => { const e = new Error('Unexpected token'); e.code = 'ERR_INVALID_JSON'; throw e; },
78
+ }));
79
+ assert(/FAIL-LOUD/.test(msg), 'manifesto ILEGÍVEL → recusa fail-loud (não é tratado como "sem blessed")');
80
+
81
+ // (adversarial) manifesto carrega mas SEM entradas de veto-checks → Set vazio → recusa.
82
+ msg = messageOf(() => svc.assertCommandCheckFrontier({
83
+ validHtmlPath: VALID_HTML,
84
+ loadMotorScripts: () => ({ motorRels: () => ['scripts/pc-run-pod.cjs', 'scripts/pod-memory.cjs'] }),
85
+ }));
86
+ assert(/FAIL-LOUD/.test(msg) && /VAZIO/.test(msg),
87
+ 'conjunto blessed VAZIO (estado em que o Guard B do motor é PULADO) → recusa fail-loud');
88
+
89
+ // filtro correto: só `scripts/veto-checks/` entra no conjunto.
90
+ const filtered = svc.blessedVetoRels({ loadMotorScripts: () => ({ motorRels: () => ['scripts/veto-checks/a.cjs', 'scripts/x.cjs'] }) });
91
+ assert(filtered.size === 1 && filtered.has('scripts/veto-checks/a.cjs'), 'blessedVetoRels filtra por scripts/veto-checks/ (paridade com o motor)');
92
+
93
+ // preflight COMPLETO (o arranque real do serviço), com env/chrome injetados.
94
+ const env = { ANTHROPIC_API_KEY: 'sk-ant-XXX', OPENROUTER_API_KEY: 'sk-or-XXX' };
95
+ const depsBase = { env, probeChrome: () => '/usr/bin/chromium', evalVeto: motor.evalVeto, validHtmlPath: VALID_HTML };
96
+ assert(throwsFail(() => svc.preflight({}, { ...depsBase, motorScriptsPath: noManifest })),
97
+ 'preflight RECUSA o arranque do serviço com o manifesto indisponível (nunca sobe degradado)');
98
+ const okSummary = svc.preflight({}, depsBase);
99
+ assert(okSummary.frontier.blessedCount > 0 && okSummary.chrome === 'chromium',
100
+ 'não-regressão: preflight com manifesto REAL passa e reporta blessedCount no sumário');
101
+ }
102
+
103
+ // ===========================================================================
104
+ // AC2 — provision-pod-export.sh passo 3 exige installer/motor-scripts.js.
105
+ // ===========================================================================
106
+ function ac2Provision() {
107
+ console.log('\n--- AC2: provision-pod-export.sh passo 3 exige installer/motor-scripts.js ---');
108
+ const text = fs.readFileSync(PROVISION_SH, 'utf8');
109
+ const m = text.match(/for f in ([\s\S]*?); do/);
110
+ assert(!!m && /pc-run-pod\.cjs/.test(m[1]), 'passo 3: lista de arquivos obrigatórios localizada no script');
111
+ const list = m[1].replace(/\\\s*\n/g, ' ').trim().split(/\s+/).filter(Boolean);
112
+
113
+ assert(list.includes('installer/motor-scripts.js'),
114
+ 'lista exige installer/motor-scripts.js (o wrapper que o motor carrega em RUNTIME)');
115
+ assert(list.includes('installer/motor-scripts.json'),
116
+ 'não-regressão: lista continua exigindo installer/motor-scripts.json');
117
+
118
+ // Simula o loop do passo 3 (mesma lista, lida do script REAL) contra dois deployments.
119
+ const missingIn = (dir) => list.filter((f) => !fs.existsSync(path.join(dir, f)));
120
+ const full = tmpDir('fw-full');
121
+ for (const f of list) {
122
+ const p = path.join(full, f);
123
+ fs.mkdirSync(path.dirname(p), { recursive: true });
124
+ fs.writeFileSync(p, 'x');
125
+ }
126
+ assert(missingIn(full).length === 0, 'não-regressão: deployment COMPLETO → passo 3 passa (MISSING=0)');
127
+
128
+ fs.unlinkSync(path.join(full, 'installer/motor-scripts.js'));
129
+ const missing = missingIn(full);
130
+ assert(missing.includes('installer/motor-scripts.js') && missing.length === 1,
131
+ 'deployment SEM installer/motor-scripts.js (só o .json) → passo 3 FALHA (AUSENTE), antes passava');
132
+ }
133
+
134
+ // ===========================================================================
135
+ // AC3 — confineToRunDir: containment por realpath (symlink interno escapando → recusa).
136
+ // ===========================================================================
137
+ function ac3Realpath() {
138
+ console.log('\n--- AC3: confineToRunDir confina por REALPATH (symlink dentro do runDir → recusado) ---');
139
+ const runDir = tmpDir('rundir');
140
+ fs.mkdirSync(path.join(runDir, 'pieces'), { recursive: true });
141
+ const piece = path.join(runDir, 'pieces', '1-capa.html');
142
+ fs.writeFileSync(piece, '<html><body>CAPA</body></html>');
143
+ const outside = tmpDir('outside');
144
+ const secret = path.join(outside, 'secret.env');
145
+ fs.writeFileSync(secret, 'ANTHROPIC_API_KEY=sk-ant-REAL');
146
+
147
+ // (não-regressão) peça legítima → MESMO abs lexical de antes (o caller serve o mesmo arquivo).
148
+ assert(ui.confineToRunDir(runDir, 'pieces/1-capa.html') === path.resolve(runDir, 'pieces', '1-capa.html'),
149
+ 'não-regressão: peça legítima confina e devolve o abs lexical inalterado');
150
+
151
+ // (adversarial) symlink de ARQUIVO dentro do runDir apontando para fora.
152
+ fs.symlinkSync(secret, path.join(runDir, 'pieces', 'leak.html'));
153
+ assert(ui.confineToRunDir(runDir, 'pieces/leak.html') === null,
154
+ 'symlink de ARQUIVO dentro do runDir → fora do runDir: RECUSADO (antes passava pelo path.resolve)');
155
+
156
+ // (adversarial) symlink de DIRETÓRIO dentro do runDir apontando para fora.
157
+ fs.symlinkSync(outside, path.join(runDir, 'esc'), 'dir');
158
+ assert(ui.confineToRunDir(runDir, 'esc/secret.env') === null, 'symlink de DIRETÓRIO escapando o runDir: RECUSADO');
159
+ assert(ui.confineToRunDir(runDir, 'esc/nao-existe.html') === null,
160
+ 'alvo inexistente SOB symlink de diretório escapando: RECUSADO (ancestral existente é resolvido)');
161
+
162
+ // (não-regressão) symlink apontando para DENTRO do runDir continua aceito.
163
+ fs.symlinkSync(piece, path.join(runDir, 'alias.html'));
164
+ assert(ui.confineToRunDir(runDir, 'alias.html') === path.resolve(runDir, 'alias.html'),
165
+ 'não-regressão: symlink interno (alvo DENTRO do runDir) continua ACEITO');
166
+
167
+ // (não-regressão) alvo inexistente sob diretório legítimo: abs (o caller responde 400 no existsSync).
168
+ assert(ui.confineToRunDir(runDir, 'pieces/nao-existe.html') === path.resolve(runDir, 'pieces', 'nao-existe.html'),
169
+ 'não-regressão: alvo inexistente sob dir legítimo devolve abs (caller responde 400)');
170
+
171
+ // (não-regressão) guardas lexicais de SF10.5 intactas.
172
+ assert(ui.confineToRunDir(runDir, '../../etc/passwd') === null, 'não-regressão: traversal ../../ continua RECUSADO');
173
+ assert(ui.confineToRunDir(runDir, 'a/../../b') === null, 'não-regressão: traversal disfarçada continua RECUSADA');
174
+ assert(ui.confineToRunDir(runDir, '') === null && ui.confineToRunDir(runDir, null) === null, 'não-regressão: relPath vazio/não-string → null');
175
+ assert(ui.confineToRunDir(runDir, 'pieces/a%00.html') === null, 'não-regressão: NUL codificado → RECUSADO');
176
+ }
177
+
178
+ // ===========================================================================
179
+ // AC4 — decodeURIComponent sob try/catch (nível de função).
180
+ // ===========================================================================
181
+ function ac4DecodeUnit() {
182
+ console.log('\n--- AC4: decodeURIComponent malformado → recusa, nunca URIError ---');
183
+ const runDir = tmpDir('uri');
184
+ let threw = false;
185
+ let out;
186
+ try { out = ui.confineToRunDir(runDir, '%zz'); } catch { threw = true; }
187
+ assert(!threw && out === null, '"%zz" em confineToRunDir → null (sem URIError propagado)');
188
+ threw = false;
189
+ try { out = ui.confineToRunDir(runDir, 'pieces/%E0%A4%A.html'); } catch { threw = true; }
190
+ assert(!threw && out === null, 'percent-encoding TRUNCADO → null (sem URIError)');
191
+ threw = false;
192
+ try { out = ui.confineToRunDir(runDir, '%'); } catch { threw = true; }
193
+ assert(!threw && out === null, '"%" solto → null (sem URIError)');
194
+
195
+ // (não-regressão) percent-encoding VÁLIDO continua sendo decodificado.
196
+ fs.writeFileSync(path.join(runDir, 'com espaço.html'), '<html></html>');
197
+ assert(ui.confineToRunDir(runDir, 'com%20espa%C3%A7o.html') === path.resolve(runDir, 'com espaço.html'),
198
+ 'não-regressão: percent-encoding VÁLIDO continua decodificando para o arquivo certo');
199
+ }
200
+
201
+ // ===========================================================================
202
+ // Fixture da UI (queue + runDir + identidades) para os testes HTTP.
203
+ // ===========================================================================
204
+ const SESSION_KEY = 'k'.repeat(48);
205
+ const TEAM_SECRET = 'senha-da-equipe';
206
+ const APPROVER_SECRET = 'segredo-do-aprovador';
207
+
208
+ function makeFixture() {
209
+ const root = tmpDir('fix');
210
+ const queueDir = path.join(root, 'queue');
211
+ const runsRoot = path.join(root, 'runs');
212
+ const bundleDir = path.join(root, 'bundle');
213
+ fs.mkdirSync(bundleDir, { recursive: true });
214
+ fs.writeFileSync(path.join(bundleDir, 'workflow.yaml'),
215
+ 'steps:\n - id: montagem\n checkpoint: { blocking: true, criteria: "7 peças" }\n');
216
+ const runId = 'pex-hn-carousel-2026';
217
+ const dirs = svc.ensureQueue(queueDir);
218
+ fs.writeFileSync(path.join(dirs.awaiting, `${runId}.json`), JSON.stringify({ pod: 'hn-carousel', started_by: 'bob' }));
219
+ fs.writeFileSync(path.join(dirs.awaiting, `${runId}.result.json`), JSON.stringify({ runId, kind: 'awaiting', started_by: 'bob', awaiting: 'montagem', at: '2026-07-31T10:00:00Z' }));
220
+ const runDir = path.join(runsRoot, 'hn-carousel', 'wf', runId);
221
+ fs.mkdirSync(path.join(runDir, 'pieces'), { recursive: true });
222
+ fs.writeFileSync(path.join(runDir, 'state.json'), JSON.stringify({ run_id: runId, steps: {} }));
223
+ fs.writeFileSync(path.join(runDir, 'pieces', '1-capa.html'), '<html><body>CAPA</body></html>');
224
+ // symlink DENTRO do runDir apontando para um segredo FORA (vetor do AC3, exercitado via HTTP).
225
+ const outside = tmpDir('outside-http');
226
+ fs.writeFileSync(path.join(outside, 'secret.env'), 'ANTHROPIC_API_KEY=sk-ant-REAL');
227
+ fs.symlinkSync(path.join(outside, 'secret.env'), path.join(runDir, 'pieces', 'leak.html'));
228
+ const identitiesFile = path.join(root, 'identities.json');
229
+ fs.writeFileSync(identitiesFile, JSON.stringify({ identities: [{ id: 'daniel', label: 'Daniel', is_approver: true }, { id: 'alice', label: 'Alice' }] }));
230
+ const cfg = ui.loadUiConfig({
231
+ PODEXPORT_QUEUE_DIR: queueDir,
232
+ PODEXPORT_RUNS_DIR: runsRoot,
233
+ PODEXPORT_BUNDLE_DIR: bundleDir,
234
+ PODEXPORT_IDENTITIES: identitiesFile,
235
+ PODEXPORT_UI_TEAM_HASH: svc.hashApproverSecret(TEAM_SECRET),
236
+ PODEXPORT_UI_SESSION_KEY: SESSION_KEY,
237
+ PODEXPORT_APPROVER_SECRET_HASH: svc.hashApproverSecret(APPROVER_SECRET),
238
+ PODEXPORT_UI_INSECURE: '1',
239
+ });
240
+ process.env.PODEXPORT_IDENTITIES = identitiesFile;
241
+ return { cfg, runId, root };
242
+ }
243
+
244
+ function request(server, { method = 'GET', pathUrl = '/', cookie, body } = {}) {
245
+ const { port } = server.address();
246
+ return new Promise((resolve, reject) => {
247
+ const headers = { Origin: `http://127.0.0.1:${port}` };
248
+ if (cookie) headers.Cookie = cookie;
249
+ if (body != null) { headers['Content-Type'] = 'application/x-www-form-urlencoded'; headers['Content-Length'] = Buffer.byteLength(body); }
250
+ const req = http.request({ host: '127.0.0.1', port, method, path: pathUrl, headers }, (res) => {
251
+ let data = ''; res.on('data', (c) => { data += c; });
252
+ res.on('end', () => resolve({ status: res.statusCode, headers: res.headers, body: data }));
253
+ });
254
+ // Timeout curto: se o handler LANÇAR (ex.: URIError não capturado) a resposta nunca vem — sem
255
+ // isto o teste PENDURA em vez de falhar. Falha explícita > espera infinita.
256
+ req.setTimeout(5000, () => req.destroy(new Error('timeout: o handler não respondeu (exceção não capturada?)')));
257
+ req.on('error', reject);
258
+ if (body != null) req.write(body);
259
+ req.end();
260
+ });
261
+ }
262
+
263
+ // ===========================================================================
264
+ // AC3+AC4 pela rota REAL /preview: 400 (nunca 500/crash), processo VIVO depois.
265
+ // ===========================================================================
266
+ async function ac4HttpPreview() {
267
+ console.log('\n--- AC3+AC4 via HTTP: /preview com entrada malformada/symlink → 400, servidor VIVO ---');
268
+ const { cfg, runId } = makeFixture();
269
+ const server = ui.createServer(cfg, {});
270
+ await new Promise((r) => server.listen(0, '127.0.0.1', r));
271
+ const uncaught = [];
272
+ const onUncaught = (e) => uncaught.push(e);
273
+ process.on('uncaughtException', onUncaught);
274
+
275
+ const login = await request(server, { method: 'POST', pathUrl: '/login', body: new URLSearchParams({ password: TEAM_SECRET }).toString() });
276
+ const cookie = String((login.headers['set-cookie'] || [''])[0]).match(/pex_ui=([^;]*)/);
277
+ const jar = cookie ? `pex_ui=${cookie[1]}` : null;
278
+ assert(!!jar, 'pré-condição: login da equipe devolve cookie de sessão');
279
+
280
+ // (não-regressão) peça legítima → 200 com a CSP de sandbox.
281
+ let res = await request(server, { pathUrl: `/preview/${runId}/pieces/1-capa.html`, cookie: jar });
282
+ assert(res.status === 200 && /CAPA/.test(res.body) && /sandbox/.test(String(res.headers['content-security-policy'])),
283
+ 'não-regressão: /preview de peça legítima → 200 com CSP sandbox');
284
+
285
+ // (adversarial AC4) `%zz` no segmento do runId → 400 (antes: URIError não capturado).
286
+ res = await request(server, { pathUrl: `/preview/%zz/pieces/1-capa.html`, cookie: jar });
287
+ assert(res.status === 400, 'runId com "%zz" → 400 (nunca URIError não capturado)');
288
+
289
+ // (adversarial AC4) `%zz` no relPath da peça (confineToRunDir) → 400.
290
+ res = await request(server, { pathUrl: `/preview/${runId}/pieces/%zz`, cookie: jar });
291
+ assert(res.status === 400, 'relPath com "%zz" → 400');
292
+
293
+ // (adversarial AC3) symlink dentro do runDir apontando para segredo FORA → 400, sem vazar conteúdo.
294
+ res = await request(server, { pathUrl: `/preview/${runId}/pieces/leak.html`, cookie: jar });
295
+ assert(res.status === 400 && !/sk-ant-REAL/.test(res.body), 'symlink escapando o runDir via /preview → 400, segredo NÃO vazado');
296
+
297
+ // (não-regressão) traversal continua barrada e o processo segue vivo/servindo.
298
+ res = await request(server, { pathUrl: `/preview/${runId}/..%2f..%2f..%2fetc%2fpasswd`, cookie: jar });
299
+ assert(res.status === 400, 'não-regressão: traversal codificada → 400');
300
+ res = await request(server, { pathUrl: '/health' });
301
+ assert(res.status === 200 && res.body === 'ok', 'servidor CONTINUA VIVO após todas as requisições malformadas');
302
+ assert(uncaught.length === 0, 'nenhuma exceção não capturada durante os ataques (URIError não derruba o processo)');
303
+
304
+ process.removeListener('uncaughtException', onUncaught);
305
+ await new Promise((r) => server.close(r));
306
+ }
307
+
308
+ // ===========================================================================
309
+ // AC5 — approveRun recusa runId inválido ANTES de qualquer efeito (sem spawn).
310
+ // ===========================================================================
311
+ async function ac5ApproveSafeRunId() {
312
+ console.log('\n--- AC5: approveRun valida safeRunId ANTES de authorizeApproval/spawn ---');
313
+ const { cfg, runId } = makeFixture();
314
+ let spawned = 0;
315
+ const spySpawn = () => {
316
+ spawned++;
317
+ const ch = new EventEmitter();
318
+ ch.stdout = new EventEmitter();
319
+ process.nextTick(() => ch.emit('exit', 0));
320
+ return ch;
321
+ };
322
+ // CREDENCIAIS VÁLIDAS de propósito: a ÚNICA razão possível de recusa é o runId. Sem o fix, o
323
+ // gate de SF10.4 autorizaria e o valor cru chegaria a buildApproveArgs/spawn.
324
+ const base = {
325
+ env: process.env,
326
+ identitiesFile: cfg.identitiesFile,
327
+ asId: 'daniel',
328
+ approverSecret: APPROVER_SECRET,
329
+ approverSecretHash: svc.hashApproverSecret(APPROVER_SECRET),
330
+ spawn: spySpawn,
331
+ log: () => {},
332
+ };
333
+
334
+ const bads = ['../../etc/passwd', '..', 'a/b', 'pex/../..', 'pex-ok/../../x', '', ' ', 'x'.repeat(300),
335
+ 'run;rm -rf /', 'run$(id)', 'pex x', 'pex..2026', 'pex\u00002026', null, undefined, 42, {}];
336
+ let allDenied = true;
337
+ for (const bad of bads) {
338
+ const r = await svc.approveRun(cfg, bad, base);
339
+ if (!(r && r.denied && r.code === 3 && /runId/.test(r.reason))) { allDenied = false; console.error(` ↳ não recusado: ${JSON.stringify(String(bad))}`); }
340
+ }
341
+ assert(allDenied, `todo runId inválido (${bads.length} vetores: traversal, separador, shell metachar, NUL, vazio, longo, não-string) → RECUSADO com razão de runId`);
342
+ assert(spawned === 0, 'NENHUM spawn ocorreu em nenhuma das recusas (recusa ANTES de qualquer efeito)');
343
+
344
+ // A recusa acontece ANTES de loadIdentities/authorizeApproval: com config de identidades ILEGÍVEL
345
+ // (que faria loadIdentities lançar) a razão devolvida ainda é a do runId.
346
+ const r2 = await svc.approveRun({ ...cfg, identitiesFile: path.join(cfg.queueDir, 'nao-existe.json') },
347
+ '../x', { ...base, env: {} });
348
+ assert(r2.denied && /runId/.test(r2.reason), 'recusa do runId precede loadIdentities/authorizeApproval (razão é a do runId)');
349
+ assert(spawned === 0, 'ainda sem spawn após o vetor pré-gate');
350
+
351
+ // Razão não ecoa o valor recebido (nada de refletir entrada não confiável nos logs/resposta).
352
+ const r3 = await svc.approveRun(cfg, '../../etc/passwd', base);
353
+ assert(!/etc\/passwd/.test(JSON.stringify(r3)), 'razão da recusa NÃO ecoa o valor recebido');
354
+
355
+ // (não-regressão) runId válido + credenciais válidas → autoriza e INVOCA o executor.
356
+ const okRes = await svc.approveRun(cfg, runId, base);
357
+ assert(okRes.code === 0 && !okRes.denied && spawned === 1,
358
+ 'não-regressão: runId válido + credenciais válidas → autorizado, executor invocado (1 spawn)');
359
+
360
+ // (não-regressão) o gate de SF10.4 continua recusando identidade/segredo errado com runId VÁLIDO.
361
+ const notApprover = await svc.approveRun(cfg, runId, { ...base, asId: 'alice' });
362
+ assert(notApprover.denied && /aprovadora designada/.test(notApprover.reason) && spawned === 1,
363
+ 'não-regressão: identidade não-aprovadora com runId válido → recusa do gate F3 (sem novo spawn)');
364
+
365
+ // safeRunId é a MESMA regra na UI e no backend (fonte única).
366
+ assert(ui.safeRunId === svc.safeRunId && svc.safeRunId('pex-abc-123') && !svc.safeRunId('../x'),
367
+ 'safeRunId é fonte ÚNICA compartilhada por UI e backend');
368
+ }
369
+
370
+ // ===========================================================================
371
+ // SF10.15 (FIND-1) — runId em forma de FLAG não passa mais pelo predicado nem chega ao spawn.
372
+ //
373
+ // Os 6 vetores são LITERALMENTE os do finding (`docs/qa/gates/sf10.9.yaml`, FIND-1). Cada um tem
374
+ // asserção DEDICADA em 3 níveis: (1) predicado, (2) `approveRun` com credenciais VÁLIDAS (a única
375
+ // razão possível de recusa é o runId), (3) rota HTTP REAL — foi por ela que o QA quebrou (spawn=1
376
+ // com argv deslocado); aqui ela prova spawn=0.
377
+ // ===========================================================================
378
+ const FIND1_VECTORS = ['--runs-root', '--input', '--lock', '--kill-during', '--help', '-rf'];
379
+
380
+ function spySpawnFactory(counter) {
381
+ return () => {
382
+ counter.n++;
383
+ const ch = new EventEmitter();
384
+ ch.stdout = new EventEmitter();
385
+ process.nextTick(() => ch.emit('exit', 0));
386
+ return ch;
387
+ };
388
+ }
389
+
390
+ async function sf1015Predicate() {
391
+ console.log('\n--- SF10.15/AC1+AC2: runId em forma de FLAG → predicado recusa e approveRun não spawna ---');
392
+ const { cfg, runId } = makeFixture();
393
+ const counter = { n: 0 };
394
+ const base = {
395
+ env: process.env,
396
+ identitiesFile: cfg.identitiesFile,
397
+ asId: 'daniel',
398
+ approverSecret: APPROVER_SECRET,
399
+ approverSecretHash: svc.hashApproverSecret(APPROVER_SECRET),
400
+ spawn: spySpawnFactory(counter),
401
+ log: () => {},
402
+ };
403
+
404
+ // (1) predicado — asserção DEDICADA por vetor (AC1).
405
+ for (const v of FIND1_VECTORS) {
406
+ assert(svc.safeRunId(v) === false, `AC1 · safeRunId(${JSON.stringify(v)}) === false (vetor do FIND-1)`);
407
+ }
408
+
409
+ // (2) approveRun com credenciais VÁLIDAS — recusa antes de qualquer efeito, spawn=0 (AC2).
410
+ for (const v of FIND1_VECTORS) {
411
+ const antes = counter.n;
412
+ const r = await svc.approveRun(cfg, v, base);
413
+ assert(r && r.denied === true && r.code === 3 && /runId/.test(r.reason) && counter.n === antes,
414
+ `AC2 · approveRun(${JSON.stringify(v)}) com credenciais VÁLIDAS → denied, code 3, SEM spawn`);
415
+ }
416
+ assert(counter.n === 0, 'AC2 · contagem TOTAL de spawn nos 6 vetores do FIND-1 = 0');
417
+
418
+ // A razão não ecoa a entrada (regra de SF10.9 mantida sob o predicado novo).
419
+ const rEcho = await svc.approveRun(cfg, '--runs-root', base);
420
+ assert(!/runs-root/.test(JSON.stringify(rEcho)), 'AC2 · razão da recusa NÃO ecoa o valor em forma de flag recebido');
421
+
422
+ // (3) não-regressão do predicado (AC4): `-` segue aceito FORA da 1ª posição; runId gerado passa.
423
+ const gerado = svc.genRunId('hn-carousel.json');
424
+ const legitimos = [gerado, runId, 'pex-abc-123', 'pex-hn-carousel-2026-07-31T10-00-00-000Z', 'a-b', 'run_1.v2', 'x'];
425
+ for (const ok of legitimos) {
426
+ assert(svc.safeRunId(ok) === true, `AC4 · runId legítimo segue aceito: ${JSON.stringify(ok)}`);
427
+ }
428
+ assert(/^pex-/.test(gerado) && gerado[0] !== '-', 'AC4 · genRunId produz sempre prefixo "pex-" (nenhum runId real começa por "-")');
429
+
430
+ // (4) não-regressão do argv montado (decisão sobre (a2), ver Dev Record): buildApproveArgs
431
+ // continua SEM separador `--` — `parseArgs` do executor (L342-353) não trata `--`, que viraria
432
+ // POSICIONAL e deslocaria o runId. O fix fica só no predicado.
433
+ const args = svc.buildApproveArgs(cfg, runId);
434
+ assert(args[1] === cfg.bundleDir && args[2] === 'approve' && args[3] === runId && !args.includes('--'),
435
+ 'AC4 · buildApproveArgs mantém o argv de SF10.4 (executor INTOCADO, sem separador "--")');
436
+
437
+ // (5) fonte ÚNICA do predicado (AC5): a UI reusa a MESMA função e não há 2ª cópia do regex.
438
+ assert(ui.safeRunId === svc.safeRunId && ui.safeRunId('--runs-root') === false,
439
+ 'AC5 · UI reusa svc.safeRunId — o estreitamento se propaga SEM editar pod-export-ui.cjs');
440
+ const defsRegex = ['pod-export-service.cjs', 'pod-export-ui.cjs']
441
+ .map((f) => (fs.readFileSync(path.join(__dirname, f), 'utf8').match(/RUNID_RE\s*=/g) || []).length)
442
+ .reduce((a, b) => a + b, 0);
443
+ assert(defsRegex === 1, `AC5 · existe UMA única definição de RUNID_RE nos scripts do pod-export (achadas: ${defsRegex})`);
444
+ }
445
+
446
+ // ===========================================================================
447
+ // SF10.15/AC3 — rota REAL: POST /approve com segredo de aprovador VÁLIDO → recusa, spawn=0.
448
+ // (Cenário literal do FIND-1, onde o QA observou spawn=1 com argv deslocado.)
449
+ // ===========================================================================
450
+ async function sf1015HttpApprove() {
451
+ console.log('\n--- SF10.15/AC3: POST /approve com runId em forma de FLAG (rota REAL) → recusa, spawn=0 ---');
452
+ const { cfg, runId } = makeFixture();
453
+ const counter = { n: 0 };
454
+ const chamadas = [];
455
+ const server = ui.createServer(cfg, {
456
+ // Delega ao approveRun REAL (guard 0a incluso) — só o `spawn` é espionado, para CONTAR o que
457
+ // teria virado processo. Rate-limit afrouxado: o alvo aqui é o predicado, não o ADR-004.
458
+ approveRun: async (c, id, deps) => {
459
+ chamadas.push(id);
460
+ return svc.approveRun(c, id, { ...deps, spawn: spySpawnFactory(counter), log: () => {} });
461
+ },
462
+ approveLimiter: ui.createRateLimiter(100, 60 * 1000),
463
+ });
464
+ await new Promise((r) => server.listen(0, '127.0.0.1', r));
465
+ const uncaught = [];
466
+ const onUncaught = (e) => uncaught.push(e);
467
+ process.on('uncaughtException', onUncaught);
468
+
469
+ const form = (obj) => new URLSearchParams(obj).toString();
470
+ const cookieFrom = (res) => {
471
+ const sc = res.headers['set-cookie'];
472
+ const m = sc && String(sc[0]).match(/pex_ui=([^;]*)/);
473
+ return m ? `pex_ui=${m[1]}` : null;
474
+ };
475
+ const sidFrom = (cookie) => {
476
+ const p = ui.verifySession(cookie.replace('pex_ui=', ''), SESSION_KEY);
477
+ return p && p.sid;
478
+ };
479
+
480
+ let res = await request(server, { method: 'POST', pathUrl: '/login', body: form({ password: TEAM_SECRET }) });
481
+ const cookie = cookieFrom(res);
482
+ assert(!!cookie, 'pré-condição: login da equipe devolve cookie de sessão');
483
+ res = await request(server, { method: 'POST', pathUrl: '/operator', cookie, body: form({ operator: 'daniel', _csrf: ui.csrfToken(sidFrom(cookie), SESSION_KEY) }) });
484
+ const cookieDaniel = cookieFrom(res) || cookie;
485
+ const csrfDaniel = ui.csrfToken(sidFrom(cookieDaniel), SESSION_KEY);
486
+ assert(res.status === 303, 'pré-condição: operador daniel (APROVADOR) atribuído à sessão');
487
+
488
+ // Cenário exato do FIND-1, vetor a vetor: segredo de aprovador VÁLIDO + runId em forma de flag.
489
+ for (const v of FIND1_VECTORS) {
490
+ const antes = counter.n;
491
+ const r = await request(server, {
492
+ method: 'POST', pathUrl: '/approve', cookie: cookieDaniel,
493
+ body: form({ run: v, secret: APPROVER_SECRET, _csrf: csrfDaniel }),
494
+ });
495
+ assert(r.status === 403 && counter.n === antes,
496
+ `AC3 · POST /approve run=${JSON.stringify(v)} + segredo VÁLIDO → 403 e ZERO spawn (antes do fix: spawn=1, argv deslocado)`);
497
+ }
498
+ assert(counter.n === 0, 'AC3 · contagem TOTAL de spawn pela rota HTTP real nos 6 vetores = 0');
499
+ assert(chamadas.length === FIND1_VECTORS.length, 'AC3 · as 6 requisições REALMENTE alcançaram approveRun (não foram barradas por CSRF/rate-limit)');
500
+ assert(ui.listCheckpoints(cfg).length === 1, 'AC3 · o run legítimo continua em awaiting/ (nenhuma aprovação ocorreu)');
501
+
502
+ // (não-regressão AC4) o MESMO caminho, com runId legítimo, ainda aprova e spawna 1 vez — prova
503
+ // que o contador de spawn é sensível (spawn=0 acima significa recusa, não teste cego).
504
+ res = await request(server, {
505
+ method: 'POST', pathUrl: '/approve', cookie: cookieDaniel,
506
+ body: form({ run: runId, secret: APPROVER_SECRET, _csrf: csrfDaniel }),
507
+ });
508
+ assert(res.status === 303 && /flash=approved/.test(String(res.headers.location)) && counter.n === 1,
509
+ 'AC4 · não-regressão pela rota real: runId legítimo + segredo válido → aprovado com exatamente 1 spawn');
510
+
511
+ res = await request(server, { pathUrl: '/health' });
512
+ assert(res.status === 200 && res.body === 'ok' && uncaught.length === 0,
513
+ 'AC3 · servidor VIVO e sem exceção não capturada após os 6 vetores em forma de flag');
514
+
515
+ process.removeListener('uncaughtException', onUncaught);
516
+ await new Promise((r) => server.close(r));
517
+ }
518
+
519
+ // ---------------------------------------------------------------------------
520
+ (async () => {
521
+ ac1PreflightBlessed();
522
+ ac2Provision();
523
+ ac3Realpath();
524
+ ac4DecodeUnit();
525
+ await ac4HttpPreview();
526
+ await ac5ApproveSafeRunId();
527
+ await sf1015Predicate();
528
+ await sf1015HttpApprove();
529
+ console.log(`\n${failures === 0 ? '[SUCESSO]' : '[FALHOU]'} pod-export hardening (SF10.9 + SF10.15): ${failures} falha(s).`);
530
+ process.exit(failures === 0 ? 0 : 1);
531
+ })().catch((e) => { console.error('[ERRO NO TESTE]', e); process.exit(1); });