software-defence-factory 0.3.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 (48) hide show
  1. package/.agents/skills/README.md +12 -0
  2. package/.agents/skills/factory-evaluate/SKILL.md +16 -0
  3. package/.agents/skills/factory-implement/SKILL.md +20 -0
  4. package/.agents/skills/factory-review/SKILL.md +16 -0
  5. package/.agents/skills/factory-security/SKILL.md +14 -0
  6. package/.agents/skills/factory-spec/SKILL.md +14 -0
  7. package/.agents/skills/factory-triage/SKILL.md +12 -0
  8. package/.github/ISSUE_TEMPLATE/factory-task.yml +35 -0
  9. package/LICENSE +21 -0
  10. package/README.md +72 -0
  11. package/THIRD_PARTY_NOTICES.md +547 -0
  12. package/bin/software-defence-factory.mjs +198 -0
  13. package/config/labels.json +37 -0
  14. package/docs/defence-integration.md +15 -0
  15. package/docs/npm.md +93 -0
  16. package/docs/ownership.md +9 -0
  17. package/docs/quickstart.md +56 -0
  18. package/factory/examples/incident.json +15 -0
  19. package/factory/examples/task.md +8 -0
  20. package/factory/executor.mjs +152 -0
  21. package/factory/image/Dockerfile +8 -0
  22. package/factory/image/mock.mjs +16 -0
  23. package/factory/incident.mjs +64 -0
  24. package/factory/lib.mjs +79 -0
  25. package/factory/paths.mjs +13 -0
  26. package/factory/pins.json +3 -0
  27. package/factory/processes.mjs +60 -0
  28. package/factory/queue.mjs +132 -0
  29. package/factory/server.mjs +101 -0
  30. package/factory/supervisor.mjs +20 -0
  31. package/factory/ui/assets/index-BWlVQO4e.js +10 -0
  32. package/factory/ui/assets/index-CpsRE53G.css +1 -0
  33. package/factory/ui/assets/manrope-cyrillic-ext-wght-normal-C8S-KRRz.woff2 +0 -0
  34. package/factory/ui/assets/manrope-cyrillic-wght-normal-Dvxsihut.woff2 +0 -0
  35. package/factory/ui/assets/manrope-greek-wght-normal-DL7QRZyv.woff2 +0 -0
  36. package/factory/ui/assets/manrope-latin-ext-wght-normal-Ch3YOpNY.woff2 +0 -0
  37. package/factory/ui/assets/manrope-latin-wght-normal-DHIcAJRg.woff2 +0 -0
  38. package/factory/ui/assets/manrope-vietnamese-wght-normal-usUDDRr7.woff2 +0 -0
  39. package/factory/ui/index.html +14 -0
  40. package/factory/updates.mjs +137 -0
  41. package/kit/README.md +25 -0
  42. package/kit/delivery.md +28 -0
  43. package/kit/examples/github-checks.yml.example +32 -0
  44. package/kit/installation.md +40 -0
  45. package/kit/policy.md +41 -0
  46. package/package.json +45 -0
  47. package/scripts/export-kit.mjs +65 -0
  48. package/scripts/probe-platform.mjs +75 -0
@@ -0,0 +1 @@
1
+ @font-face{font-family:Manrope Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/manrope-cyrillic-ext-wght-normal-C8S-KRRz.woff2)format("woff2-variations");unicode-range:U+460-52F,U+1C80-1C8A,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Manrope Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/manrope-cyrillic-wght-normal-Dvxsihut.woff2)format("woff2-variations");unicode-range:U+301,U+400-45F,U+490-491,U+4B0-4B1,U+2116}@font-face{font-family:Manrope Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/manrope-greek-wght-normal-DL7QRZyv.woff2)format("woff2-variations");unicode-range:U+370-377,U+37A-37F,U+384-38A,U+38C,U+38E-3A1,U+3A3-3FF}@font-face{font-family:Manrope Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/manrope-vietnamese-wght-normal-usUDDRr7.woff2)format("woff2-variations");unicode-range:U+102-103,U+110-111,U+128-129,U+168-169,U+1A0-1A1,U+1AF-1B0,U+300-301,U+303-304,U+308-309,U+323,U+329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Manrope Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/manrope-latin-ext-wght-normal-Ch3YOpNY.woff2)format("woff2-variations");unicode-range:U+100-2BA,U+2BD-2C5,U+2C7-2CC,U+2CE-2D7,U+2DD-2FF,U+304,U+308,U+329,U+1D00-1DBF,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Manrope Variable;font-style:normal;font-display:swap;font-weight:200 800;src:url(/assets/manrope-latin-wght-normal-DHIcAJRg.woff2)format("woff2-variations");unicode-range:U+??,U+131,U+152-153,U+2BB-2BC,U+2C6,U+2DA,U+2DC,U+304,U+308,U+329,U+2000-206F,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-space-y-reverse:0;--tw-divide-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-ordinal:initial;--tw-slashed-zero:initial;--tw-numeric-figure:initial;--tw-numeric-spacing:initial;--tw-numeric-fraction:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-sans:"Manrope Variable", ui-sans-serif, system-ui, sans-serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--spacing:.25rem;--container-sm:24rem;--container-2xl:42rem;--container-4xl:56rem;--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-3xl:1.875rem;--text-3xl--line-height:calc(2.25 / 1.875);--text-4xl:2.25rem;--text-4xl--line-height:calc(2.5 / 2.25);--text-5xl:3rem;--text-5xl--line-height:1;--font-weight-medium:500;--font-weight-semibold:600;--tracking-tight:-.025em;--tracking-wider:.05em;--radius-sm:.25rem;--radius-md:.375rem;--radius-lg:.5rem;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-font-family:"Manrope Variable", ui-sans-serif, system-ui, sans-serif;--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;tab-size:4;line-height:1.5;font-family:var(--default-font-family,-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", "Noto Sans", Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji");font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring:where(:not(iframe)){outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab, red, red)){::placeholder{color:color-mix(in oklab, currentcolor 50%, transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){appearance:button}::file-selector-button{appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.visible{visibility:visible}.sticky{position:sticky}.top-0{top:0}.z-20{z-index:20}.mx-auto{margin-inline:auto}.my-4{margin-block:calc(var(--spacing) * 4)}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:var(--spacing)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mt-6{margin-top:calc(var(--spacing) * 6)}.-mb-px{margin-bottom:-1px}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.-ml-3{margin-left:calc(var(--spacing) * -3)}.ml-4{margin-left:calc(var(--spacing) * 4)}.ml-auto{margin-left:auto}.line-clamp-2{-webkit-line-clamp:2;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.line-clamp-3{-webkit-line-clamp:3;-webkit-box-orient:vertical;display:-webkit-box;overflow:hidden}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.inline-flex{display:inline-flex}.table{display:table}.size-1\.5{width:calc(var(--spacing) * 1.5);height:calc(var(--spacing) * 1.5)}.size-3\.5{width:calc(var(--spacing) * 3.5);height:calc(var(--spacing) * 3.5)}.size-4{width:calc(var(--spacing) * 4);height:calc(var(--spacing) * 4)}.size-5{width:calc(var(--spacing) * 5);height:calc(var(--spacing) * 5)}.size-7{width:calc(var(--spacing) * 7);height:calc(var(--spacing) * 7)}.size-9{width:calc(var(--spacing) * 9);height:calc(var(--spacing) * 9)}.size-10{width:calc(var(--spacing) * 10);height:calc(var(--spacing) * 10)}.h-7{height:calc(var(--spacing) * 7)}.h-8{height:calc(var(--spacing) * 8)}.h-9{height:calc(var(--spacing) * 9)}.h-10{height:calc(var(--spacing) * 10)}.max-h-96{max-height:calc(var(--spacing) * 96)}.max-h-\[60vh\]{max-height:60vh}.min-h-24{min-height:calc(var(--spacing) * 24)}.min-h-32{min-height:calc(var(--spacing) * 32)}.min-h-48{min-height:calc(var(--spacing) * 48)}.min-h-screen{min-height:100vh}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-4xl{max-width:var(--container-4xl)}.max-w-\[1000px\]{max-width:1000px}.max-w-\[1500px\]{max-width:1500px}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:0}.flex-1{flex:1}.shrink-0{flex-shrink:0}.resize-y{resize:vertical}.grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-cols-\[minmax\(8rem\,1fr\)_minmax\(8rem\,1fr\)_10rem_12rem\]{grid-template-columns:minmax(8rem,1fr) minmax(8rem,1fr) 10rem 12rem}.flex-col{flex-direction:column}.flex-wrap{flex-wrap:wrap}.place-items-center{place-items:center}.items-center{align-items:center}.items-end{align-items:flex-end}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.justify-end{justify-content:flex-end}.gap-1{gap:var(--spacing)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-7>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 7) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 7) * calc(1 - var(--tw-space-y-reverse)))}.gap-x-6{column-gap:calc(var(--spacing) * 6)}.gap-y-3{row-gap:calc(var(--spacing) * 3)}:where(.divide-y>:not(:last-child)){--tw-divide-y-reverse:0;border-bottom-style:var(--tw-border-style);border-top-style:var(--tw-border-style);border-top-width:calc(1px * var(--tw-divide-y-reverse));border-bottom-width:calc(1px * calc(1 - var(--tw-divide-y-reverse)))}:where(.divide-border>:not(:last-child)){border-color:var(--border)}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-auto{overflow:auto}.overflow-hidden{overflow:hidden}.overflow-x-auto{overflow-x:auto}.rounded-full{border-radius:2147483647px}.rounded-lg{border-radius:var(--radius-lg)}.rounded-md{border-radius:var(--radius-md)}.rounded-sm{border-radius:var(--radius-sm)}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-l-2{border-left-style:var(--tw-border-style);border-left-width:2px}.border-border,.border-border\/70{border-color:var(--border)}@supports (color:color-mix(in lab, red, red)){.border-border\/70{border-color:color-mix(in oklab, var(--border) 70%, transparent)}}.border-danger\/25{border-color:var(--danger)}@supports (color:color-mix(in lab, red, red)){.border-danger\/25{border-color:color-mix(in oklab, var(--danger) 25%, transparent)}}.border-danger\/35{border-color:var(--danger)}@supports (color:color-mix(in lab, red, red)){.border-danger\/35{border-color:color-mix(in oklab, var(--danger) 35%, transparent)}}.border-danger\/40{border-color:var(--danger)}@supports (color:color-mix(in lab, red, red)){.border-danger\/40{border-color:color-mix(in oklab, var(--danger) 40%, transparent)}}.border-primary,.border-primary\/25{border-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.border-primary\/25{border-color:color-mix(in oklab, var(--primary) 25%, transparent)}}.border-success\/25{border-color:var(--success)}@supports (color:color-mix(in lab, red, red)){.border-success\/25{border-color:color-mix(in oklab, var(--success) 25%, transparent)}}.border-transparent{border-color:#0000}.border-warning\/25{border-color:var(--warning)}@supports (color:color-mix(in lab, red, red)){.border-warning\/25{border-color:color-mix(in oklab, var(--warning) 25%, transparent)}}.bg-background{background-color:var(--background)}.bg-current{background-color:currentColor}.bg-danger\/5{background-color:var(--danger)}@supports (color:color-mix(in lab, red, red)){.bg-danger\/5{background-color:color-mix(in oklab, var(--danger) 5%, transparent)}}.bg-danger\/10{background-color:var(--danger)}@supports (color:color-mix(in lab, red, red)){.bg-danger\/10{background-color:color-mix(in oklab, var(--danger) 10%, transparent)}}.bg-muted,.bg-muted\/20{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/20{background-color:color-mix(in oklab, var(--muted) 20%, transparent)}}.bg-muted\/35{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/35{background-color:color-mix(in oklab, var(--muted) 35%, transparent)}}.bg-muted\/40{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/40{background-color:color-mix(in oklab, var(--muted) 40%, transparent)}}.bg-muted\/60{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.bg-muted\/60{background-color:color-mix(in oklab, var(--muted) 60%, transparent)}}.bg-primary,.bg-primary\/5{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.bg-primary\/5{background-color:color-mix(in oklab, var(--primary) 5%, transparent)}}.bg-sidebar{background-color:var(--sidebar)}.bg-success\/10{background-color:var(--success)}@supports (color:color-mix(in lab, red, red)){.bg-success\/10{background-color:color-mix(in oklab, var(--success) 10%, transparent)}}.bg-surface{background-color:var(--surface)}.bg-transparent{background-color:#0000}.bg-warning\/10{background-color:var(--warning)}@supports (color:color-mix(in lab, red, red)){.bg-warning\/10{background-color:color-mix(in oklab, var(--warning) 10%, transparent)}}.p-1{padding:var(--spacing)}.p-2{padding:calc(var(--spacing) * 2)}.p-4{padding:calc(var(--spacing) * 4)}.p-5{padding:calc(var(--spacing) * 5)}.p-8{padding:calc(var(--spacing) * 8)}.p-12{padding:calc(var(--spacing) * 12)}.px-0{padding-inline:0}.px-1{padding-inline:var(--spacing)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-2\.5{padding-inline:calc(var(--spacing) * 2.5)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-3\.5{padding-inline:calc(var(--spacing) * 3.5)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-5{padding-inline:calc(var(--spacing) * 5)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:var(--spacing)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-3\.5{padding-block:calc(var(--spacing) * 3.5)}.py-4{padding-block:calc(var(--spacing) * 4)}.py-8{padding-block:calc(var(--spacing) * 8)}.py-14{padding-block:calc(var(--spacing) * 14)}.py-16{padding-block:calc(var(--spacing) * 16)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pl-4{padding-left:calc(var(--spacing) * 4)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.font-sans{font-family:Manrope Variable,ui-sans-serif,system-ui,sans-serif}.text-2xl{font-size:2.25rem;line-height:var(--tw-leading,2.5rem)}.text-4xl{font-size:var(--text-4xl);line-height:var(--tw-leading,var(--text-4xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:.875rem;line-height:var(--tw-leading,1.25rem)}.text-xl{font-size:1.375rem;line-height:var(--tw-leading,1.75rem)}.text-xs{font-size:.75rem;line-height:var(--tw-leading,1rem)}.text-xs\!{font-size:.75rem!important;line-height:var(--tw-leading,1rem)!important}.leading-5{--tw-leading:calc(var(--spacing) * 5);line-height:calc(var(--spacing) * 5)}.leading-6{--tw-leading:calc(var(--spacing) * 6);line-height:calc(var(--spacing) * 6)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.font-semibold{--tw-font-weight:var(--font-weight-semibold);font-weight:var(--font-weight-semibold)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-words{overflow-wrap:break-word}.break-all{word-break:break-all}.whitespace-nowrap{white-space:nowrap}.whitespace-pre-wrap{white-space:pre-wrap}.text-danger{color:var(--danger)}.text-foreground{color:var(--foreground)}.text-muted-foreground{color:var(--muted-foreground)}.text-primary{color:var(--primary)}.text-primary-foreground{color:var(--primary-foreground)}.text-success{color:var(--success)}.text-warning{color:var(--warning)}.capitalize{text-transform:capitalize}.uppercase{text-transform:uppercase}.tabular-nums{--tw-numeric-spacing:tabular-nums;font-variant-numeric:var(--tw-ordinal,) var(--tw-slashed-zero,) var(--tw-numeric-figure,) var(--tw-numeric-spacing,) var(--tw-numeric-fraction,)}.underline{text-decoration-line:underline}.underline-offset-4{text-underline-offset:4px}.shadow-xs{--tw-shadow:0 1px 2px var(--tw-shadow-color,#0f172a0d);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.last\:border-0:last-child{border-style:var(--tw-border-style);border-width:0}.last\:border-b-0:last-child{border-bottom-style:var(--tw-border-style);border-bottom-width:0}@media (hover:hover){.hover\:bg-muted:hover,.hover\:bg-muted\/35:hover{background-color:var(--muted)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-muted\/35:hover{background-color:color-mix(in oklab, var(--muted) 35%, transparent)}}.hover\:bg-primary\/90:hover{background-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.hover\:bg-primary\/90:hover{background-color:color-mix(in oklab, var(--primary) 90%, transparent)}}.hover\:text-danger:hover{color:var(--danger)}.hover\:text-foreground:hover{color:var(--foreground)}.hover\:decoration-2:hover{text-decoration-thickness:2px}}.focus-visible\:ring-2:focus-visible{--tw-ring-shadow:var(--tw-ring-inset,) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color,currentcolor);box-shadow:var(--tw-inset-shadow), var(--tw-inset-ring-shadow), var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow)}.focus-visible\:ring-ring\/40:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-ring\/40:focus-visible{--tw-ring-color:color-mix(in oklab, var(--ring) 40%, transparent)}}.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:var(--ring)}@supports (color:color-mix(in lab, red, red)){.focus-visible\:ring-ring\/50:focus-visible{--tw-ring-color:color-mix(in oklab, var(--ring) 50%, transparent)}}.focus-visible\:outline-2:focus-visible{outline-style:var(--tw-outline-style);outline-width:2px}.focus-visible\:outline-offset-2:focus-visible{outline-offset:2px}.focus-visible\:outline-ring:focus-visible{outline-color:var(--ring)}.focus-visible\:outline-none:focus-visible{--tw-outline-style:none;outline-style:none}.focus-visible\:ring-inset:focus-visible{--tw-ring-inset:inset}.disabled\:pointer-events-none:disabled{pointer-events:none}.disabled\:opacity-50:disabled{opacity:.5}.disabled\:opacity-60:disabled{opacity:.6}@media (width>=40rem){.sm\:grid{display:grid}.sm\:hidden{display:none}.sm\:w-40{width:calc(var(--spacing) * 40)}.sm\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.sm\:grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.sm\:grid-cols-\[minmax\(8rem\,1fr\)_minmax\(8rem\,1fr\)_10rem_12rem\]{grid-template-columns:minmax(8rem,1fr) minmax(8rem,1fr) 10rem 12rem}.sm\:grid-cols-\[minmax\(12rem\,1fr\)_minmax\(12rem\,1fr\)_10rem\]{grid-template-columns:minmax(12rem,1fr) minmax(12rem,1fr) 10rem}.sm\:flex-col{flex-direction:column}.sm\:flex-row{flex-direction:row}.sm\:items-center{align-items:center}.sm\:items-end{align-items:flex-end}.sm\:items-start{align-items:flex-start}.sm\:justify-between{justify-content:space-between}.sm\:gap-4{gap:calc(var(--spacing) * 4)}.sm\:p-5{padding:calc(var(--spacing) * 5)}.sm\:p-6{padding:calc(var(--spacing) * 6)}.sm\:px-5{padding-inline:calc(var(--spacing) * 5)}.sm\:text-right{text-align:right}.sm\:text-3xl{font-size:var(--text-3xl);line-height:var(--tw-leading,var(--text-3xl--line-height))}.sm\:text-5xl{font-size:var(--text-5xl);line-height:var(--tw-leading,var(--text-5xl--line-height))}.sm\:text-sm{font-size:.875rem;line-height:var(--tw-leading,1.25rem)}}@media (width>=48rem){.md\:mt-9{margin-top:calc(var(--spacing) * 9)}.md\:ml-0{margin-left:0}.md\:block{display:block}.md\:flex{display:flex}.md\:hidden{display:none}.md\:h-screen{height:100vh}.md\:w-56{width:calc(var(--spacing) * 56)}.md\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.md\:flex-col{flex-direction:column}.md\:items-stretch{align-items:stretch}.md\:overflow-visible{overflow:visible}.md\:border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.md\:border-b-0{border-bottom-style:var(--tw-border-style);border-bottom-width:0}.md\:px-4{padding-inline:calc(var(--spacing) * 4)}.md\:py-5{padding-block:calc(var(--spacing) * 5)}}@media (width>=64rem){.lg\:grid-cols-\[minmax\(0\,1\.4fr\)_minmax\(0\,1fr\)\]{grid-template-columns:minmax(0,1.4fr) minmax(0,1fr)}.lg\:flex-row{flex-direction:row}.lg\:items-center{align-items:center}.lg\:justify-between{justify-content:space-between}.lg\:justify-end{justify-content:flex-end}.lg\:p-8{padding:calc(var(--spacing) * 8)}}@media (width>=80rem){.xl\:grid-cols-2{grid-template-columns:repeat(2,minmax(0,1fr))}.xl\:grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}}}:root{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light;--background:#f3f4f1;--foreground:#1a1d1e;--surface:#fafbf8;--sidebar:#e8eae7;--muted:#e4e7e4;--muted-foreground:#626a6b;--border:#c7cdca;--primary:#1d6683;--primary-foreground:#f8fcfd;--success:#326b51;--warning:#8a651b;--danger:#a54237;--ring:#1d6683}.dark{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark;--background:#151819;--foreground:#edf1ef;--surface:#1c2021;--sidebar:#111415;--muted:#252a2b;--muted-foreground:#9ba4a4;--border:#373e3e;--primary:#66a9c2;--primary-foreground:#102128;--success:#78ad91;--warning:#d0a75c;--danger:#df8075;--ring:#66a9c2}*{border-color:var(--border)}html{background:var(--background)}body{background:var(--background);min-width:320px;min-height:100vh;color:var(--foreground);font-family:var(--font-sans);-webkit-font-smoothing:antialiased;margin:0}button,input,textarea,select{font:inherit}button{cursor:pointer}button:disabled{cursor:not-allowed}::selection{background:var(--primary)}@supports (color:color-mix(in lab, red, red)){::selection{background:color-mix(in srgb, var(--primary) 25%, transparent)}}.app-sidebar{box-shadow:inset -1px 0 var(--foreground)}@supports (color:color-mix(in lab, red, red)){.app-sidebar{box-shadow:inset -1px 0 color-mix(in srgb, var(--foreground) 4%, transparent)}}.workshop{background:var(--background)}.brand-wordmark{font-family:var(--font-sans);letter-spacing:-.02em;font-size:1.125rem;font-weight:600}.factory-mark{width:2.6rem;height:auto;color:var(--foreground);overflow:visible}.factory-mark path{fill:none;stroke-width:5px;stroke-linecap:square;stroke-linejoin:round}.factory-mark .factory-mark-piece-a{stroke:currentColor}.factory-mark .factory-mark-piece-b{stroke:var(--primary)}.page-heading{border-bottom:1px solid var(--border);padding-bottom:1.35rem}main h1{font-family:var(--font-sans);letter-spacing:-.02em;font-size:1.5rem;font-weight:600;line-height:1.35}main h2{letter-spacing:-.012em}.nav-item{height:2.4rem;color:var(--muted-foreground);border-radius:0;flex-shrink:0;align-items:center;gap:.625rem;padding:0 .625rem;font-size:.8125rem;line-height:1.25rem;transition:color .15s,background .15s;display:flex;position:relative}.nav-item:hover{background:var(--muted)}@supports (color:color-mix(in lab, red, red)){.nav-item:hover{background:color-mix(in srgb, var(--muted) 65%, transparent)}}.nav-item:hover{color:var(--foreground)}.nav-item-active{color:var(--foreground);box-shadow:none;background:0 0}.nav-item-active:before{background:var(--primary);content:"";width:2px;height:1.15rem;position:absolute;left:-.25rem}.crafted-card{box-shadow:0 1px 0 var(--foreground), 0 10px 30px var(--foreground)}@supports (color:color-mix(in lab, red, red)){.crafted-card{box-shadow:0 1px 0 color-mix(in srgb, var(--foreground) 6%, transparent), 0 10px 30px color-mix(in srgb, var(--foreground) 3%, transparent)}}.run-column{border-radius:8px}.quiet-state{min-height:11rem}.quiet-state-mark{height:1.25rem;color:var(--primary);align-items:flex-end;gap:3px;display:flex}.quiet-state-mark i{background:currentColor;width:2px;height:.65rem;display:block}.quiet-state-mark i:nth-child(2){height:1.2rem}.quiet-state-mark i:nth-child(3){height:.9rem}.field-label{color:var(--muted-foreground);letter-spacing:normal;margin-bottom:.375rem;font-size:.75rem;font-weight:500;line-height:1rem;display:block}.field-control{border:1px solid var(--border);background:var(--background);width:100%;min-height:2.5rem;color:var(--foreground);box-shadow:inset 0 1px 0 var(--foreground);border-radius:8px;outline:none;padding:.625rem .75rem;font-size:.875rem;line-height:1.25rem}@supports (color:color-mix(in lab, red, red)){.field-control{box-shadow:inset 0 1px 0 color-mix(in srgb, var(--foreground) 3%, transparent)}}.field-control{transition:border-color .15s,box-shadow .15s}.field-control::placeholder{color:var(--muted-foreground);opacity:.7}.field-control:focus{border-color:var(--primary)}@supports (color:color-mix(in lab, red, red)){.field-control:focus{border-color:color-mix(in srgb, var(--primary) 60%, transparent)}}.field-control:focus{box-shadow:0 0 0 3px var(--primary)}@supports (color:color-mix(in lab, red, red)){.field-control:focus{box-shadow:0 0 0 3px color-mix(in srgb, var(--primary) 14%, transparent)}}@media (width<=767px){.brand-wordmark{display:none}body{padding-bottom:calc(4.15rem + env(safe-area-inset-bottom))}.app-sidebar{box-shadow:inset 0 -1px var(--foreground)}@supports (color:color-mix(in lab, red, red)){.app-sidebar{box-shadow:inset 0 -1px color-mix(in srgb, var(--foreground) 4%, transparent)}}.app-sidebar nav{z-index:40;border-top:1px solid var(--border);background:var(--sidebar);grid-template-columns:repeat(5,minmax(0,1fr));gap:0;margin:0;display:grid;position:fixed;bottom:0;left:0;right:0;overflow:visible}@supports (color:color-mix(in lab, red, red)){.app-sidebar nav{background:color-mix(in srgb, var(--sidebar) 94%, transparent)}}.app-sidebar nav{padding:.4rem .3rem max(.4rem, env(safe-area-inset-bottom));-webkit-backdrop-filter:blur(14px);backdrop-filter:blur(14px)}.app-sidebar nav .nav-item{flex-direction:column;justify-content:center;gap:.2rem;height:3.1rem;padding:0 .1rem;font-size:.625rem}.app-sidebar nav .nav-item>.ml-auto{display:none}.app-sidebar nav .nav-item-active:before{width:1.5rem;height:2px;top:-.4rem;left:50%;transform:translate(-50%)}.mobile-theme{border:1px solid #0000}.mobile-theme:hover{border-color:var(--border);color:var(--foreground)}}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important}}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-divide-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-ordinal{syntax:"*";inherits:false}@property --tw-slashed-zero{syntax:"*";inherits:false}@property --tw-numeric-figure{syntax:"*";inherits:false}@property --tw-numeric-spacing{syntax:"*";inherits:false}@property --tw-numeric-fraction{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
@@ -0,0 +1,14 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="color-scheme" content="dark" />
7
+ <title>Software &amp; Defence Factory</title>
8
+ <script type="module" crossorigin src="/assets/index-BWlVQO4e.js"></script>
9
+ <link rel="stylesheet" crossorigin href="/assets/index-CpsRE53G.css">
10
+ </head>
11
+ <body>
12
+ <div id="root"></div>
13
+ </body>
14
+ </html>
@@ -0,0 +1,137 @@
1
+ import { existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { join, resolve } from 'node:path';
3
+ import { spawnSync } from 'node:child_process';
4
+ import { createHash } from 'node:crypto';
5
+ import { ROOT, STATE_HOME, DATA_HOME, SOURCE_CHECKOUT } from './paths.mjs';
6
+
7
+ const PACKAGE = 'software-defence-factory';
8
+ const REGISTRY = 'https://registry.npmjs.org';
9
+ const DAY = 24 * 60 * 60 * 1000;
10
+ export const VERSION = JSON.parse(readFileSync(join(ROOT, 'package.json'), 'utf8')).version;
11
+ const read = path => JSON.parse(readFileSync(path, 'utf8'));
12
+ function save(path, value) {
13
+ const temporary = `${path}.${process.pid}.tmp`;
14
+ writeFileSync(temporary, JSON.stringify(value, null, 2) + '\n', { mode: 0o600 });
15
+ renameSync(temporary, path);
16
+ }
17
+ function privateDir(path) { mkdirSync(path, { recursive: true, mode: 0o700 }); }
18
+ export function newer(candidate, current) {
19
+ if (!/^\d+\.\d+\.\d+$/.test(candidate) || !/^\d+\.\d+\.\d+$/.test(current)) return false;
20
+ const a = candidate.split('.').map(Number), b = current.split('.').map(Number);
21
+ for (let i = 0; i < 3; i++) if (a[i] !== b[i]) return a[i] > b[i];
22
+ return false;
23
+ }
24
+ export function registerInstallation(state, home = STATE_HOME) {
25
+ const directory = join(home, 'installations');
26
+ privateDir(directory);
27
+ const absolute = resolve(state);
28
+ save(join(directory, createHash('sha256').update(absolute).digest('hex') + '.json'), { state: absolute });
29
+ }
30
+ function alive(pid) {
31
+ if (!Number.isSafeInteger(pid) || pid < 1) return true;
32
+ try { process.kill(pid, 0); return true; } catch (error) { return error.code !== 'ESRCH'; }
33
+ }
34
+ export function busyInstallations(home = STATE_HOME) {
35
+ const directory = join(home, 'installations');
36
+ if (!existsSync(directory)) return [];
37
+ const busy = [];
38
+ for (const file of readdirSync(directory).filter(name => name.endsWith('.json'))) {
39
+ try {
40
+ const { state } = read(join(directory, file));
41
+ const lock = join(state, 'supervisor.json');
42
+ if (existsSync(lock) && alive(read(lock).pid)) { busy.push(state); continue; }
43
+ const jobs = join(state, 'jobs');
44
+ if (existsSync(jobs)) for (const job of readdirSync(jobs)) {
45
+ // An executor fence requires reconciliation even after its PID exits.
46
+ if (existsSync(join(jobs, job, 'active.json'))) { busy.push(state); break; }
47
+ }
48
+ } catch { busy.push(`Unverified installation: ${file}`); }
49
+ }
50
+ return busy;
51
+ }
52
+ export async function latestVersion(fetcher = fetch) {
53
+ const response = await fetcher(`${REGISTRY}/${PACKAGE}/latest`, { signal: AbortSignal.timeout(4000) });
54
+ if (!response.ok) throw new Error(`npm registry returned HTTP ${response.status}`);
55
+ const release = await response.json();
56
+ if (release.name !== PACKAGE || !/^\d+\.\d+\.\d+$/.test(release.version)) throw new Error('Unexpected npm release identity');
57
+ return release.version;
58
+ }
59
+ function installedEntry(dataHome, version) {
60
+ if (!/^\d+\.\d+\.\d+$/.test(version || '')) return null;
61
+ const root = join(dataHome, 'releases', version, 'node_modules', PACKAGE);
62
+ try {
63
+ const pkg = read(join(root, 'package.json'));
64
+ const entry = join(root, 'bin/software-defence-factory.mjs');
65
+ return pkg.name === PACKAGE && pkg.version === version && existsSync(entry) ? entry : null;
66
+ } catch { return null; }
67
+ }
68
+ export function installRelease(version, dataHome = DATA_HOME, runner = spawnSync) {
69
+ if (!/^\d+\.\d+\.\d+$/.test(version)) throw new Error('Invalid release version');
70
+ const existing = installedEntry(dataHome, version);
71
+ if (existing) return existing;
72
+ const releases = join(dataHome, 'releases'); privateDir(releases);
73
+ const staging = mkdtempSync(join(releases, '.download-'));
74
+ try {
75
+ const result = runner('npm', ['install', '--prefix', staging, '--no-save', '--package-lock=false',
76
+ '--ignore-scripts', '--omit=dev', '--no-audit', '--no-fund', '--registry', REGISTRY, `${PACKAGE}@${version}`],
77
+ { encoding: 'utf8', timeout: 120000, maxBuffer: 1024 * 1024 });
78
+ if (result.error || result.status !== 0) throw new Error(`npm update download failed: ${result.error?.message || result.stderr || result.status}`);
79
+ const pkg = read(join(staging, 'node_modules', PACKAGE, 'package.json'));
80
+ if (pkg.name !== PACKAGE || pkg.version !== version || !existsSync(join(staging, 'node_modules', PACKAGE, 'bin/software-defence-factory.mjs')))
81
+ throw new Error('Downloaded package identity does not match the requested release');
82
+ try { renameSync(staging, join(releases, version)); }
83
+ catch (error) { if (!installedEntry(dataHome, version)) throw error; }
84
+ return installedEntry(dataHome, version);
85
+ } finally { rmSync(staging, { recursive: true, force: true }); }
86
+ }
87
+
88
+ export async function bootstrap(args) {
89
+ if (process.env.SDF_BOOTSTRAPPED === '1') return;
90
+ const command = args[0] || 'help', explicit = command === 'update';
91
+ if (SOURCE_CHECKOUT) {
92
+ if (explicit) throw new Error('This is a source checkout. Install the npm CLI to use update; source changes stay under Git.');
93
+ return;
94
+ }
95
+ privateDir(STATE_HOME);
96
+ const preferencePath = join(STATE_HOME, 'updates.json');
97
+ const preferences = existsSync(preferencePath) ? read(preferencePath) : { enabled: true, lastCheckedAt: 0 };
98
+ if (explicit && args[1] === '--auto') {
99
+ if (args.length !== 3 || !['on', 'off'].includes(args[2])) throw new Error('Use update --auto on|off');
100
+ preferences.enabled = args[2] === 'on'; save(preferencePath, preferences);
101
+ console.log(`Automatic CLI updates ${preferences.enabled ? 'enabled' : 'disabled'}.`); return 0;
102
+ }
103
+ if (explicit && args.length > 1 && !(args.length === 2 && args[1] === '--check')) throw new Error('Use update, update --check, or update --auto on|off');
104
+ const busy = busyInstallations();
105
+ const cachedVersion = installedEntry(DATA_HOME, preferences.version) ? preferences.version : VERSION;
106
+ let selectedVersion = newer(cachedVersion, VERSION) ? cachedVersion : VERSION;
107
+ const automatic = !['stop', 'cancel', 'status', 'serve', 'version', '--version', '-v'].includes(command)
108
+ && preferences.enabled && process.env.SDF_AUTO_UPDATE !== '0'
109
+ && Date.now() - preferences.lastCheckedAt >= DAY && busy.length === 0;
110
+ if (explicit || automatic) {
111
+ try {
112
+ if (busy.length && args[1] !== '--check') throw new Error('Stop/reconcile running installations before updating: ' + busy.join(', '));
113
+ preferences.lastCheckedAt = Date.now(); save(preferencePath, preferences);
114
+ const latest = await latestVersion();
115
+ if (explicit && args[1] === '--check') { console.log(JSON.stringify({ current: selectedVersion, latest, available: newer(latest, selectedVersion), automatic: preferences.enabled })); return 0; }
116
+ if (newer(latest, selectedVersion)) {
117
+ console.error(`Updating ${PACKAGE} ${selectedVersion} → ${latest}…`);
118
+ installRelease(latest);
119
+ // Downloads are immutable. A newly started controller keeps its version;
120
+ // do not switch the next CLI process to a new release while it is active.
121
+ if (busyInstallations().length) throw new Error('Update downloaded; activation deferred while an installation is running.');
122
+ preferences.version = latest; save(preferencePath, preferences); selectedVersion = latest;
123
+ }
124
+ if (explicit) { console.log(`${PACKAGE} ${selectedVersion} is up to date. Runtime, credentials and jobs were preserved.`); return 0; }
125
+ } catch (error) {
126
+ if (explicit) throw error;
127
+ // An offline registry must never prevent a local command from working.
128
+ console.error(`Automatic CLI update deferred: ${error.message}`);
129
+ }
130
+ }
131
+ if (newer(selectedVersion, VERSION)) {
132
+ const entry = installedEntry(DATA_HOME, selectedVersion);
133
+ const result = spawnSync(process.execPath, [entry, ...args], { stdio: 'inherit', env: { ...process.env, SDF_BOOTSTRAPPED: '1' } });
134
+ if (result.error) throw result.error;
135
+ return result.status ?? 1;
136
+ }
137
+ }
package/kit/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # Adopt the factory method
2
+
3
+ Software & Defence Factory gives an existing app a shared method for development, security and review. Keep the app's architecture, code, CI, hosting and established instructions.
4
+
5
+ ## With the local runtime
6
+
7
+ Use the CLI's `init` and installation workflow. Jobs receive policy and the six skills directly as read-only mounts. No global skill installation or manual copying into the app is required. Initializing an installation does not start work.
8
+
9
+ ## With your existing agent
10
+
11
+ Export a new staging directory with `software-defence-factory kit --output NEW_DIRECTORY`. Give that directory to the app's agent and ask it to adopt the relevant method on a branch:
12
+
13
+ > Read the app's instructions, architecture, tests and CI first. Apply the supplied factory method within its existing authority. Fill the installation record from the repository and known choices, merge intentionally, preserve existing files and report what was connected, tested or still missing. Start manually. Accounts, publishing and deployment follow my existing mandate.
14
+
15
+ The agent can reference the staged instructions or intentionally adopt relevant files. If copying is appropriate, review `.factory-kit/` and the needed `.agents/skills/factory-*` folders; merge naming conflicts and preserve AGENTS.md. Add a local route only when needed. The optional issue form and CI example are inactive until deliberately adapted. No secret belongs in the installation record.
16
+
17
+ The six skills cover triage, specification, implementation, review, security and evaluation. Use the ones needed; six instructions do not imply six concurrent agents. See [policy](policy.md), [installation](installation.md) and [delivery](delivery.md).
18
+
19
+ ## First real task
20
+
21
+ Choose a small existing defect or improvement. Describe the intended user behavior, allowed scope and observable acceptance check. Implement a working vertical slice, exercise the app's relevant checks and obtain a separate review of the delivered revision. A demo or copied skill files alone do not qualify an application.
22
+
23
+ A PR requires the configured GitHub authority. Otherwise hand back the branch/diff and evidence with an honest status. Unknown cost/time remain unknown. Automated starts require separate qualification of triggers, deduplication, stop/restart and actual resource limits; a skill does not create a scheduler.
24
+
25
+ Update by exporting a new version and reviewing the differences. Preserve the app's completed installation record. The manifest records source revision and file hashes; it is provenance, not a signature or active runtime configuration. MIT applies to this method and does not change the app's license.
@@ -0,0 +1,28 @@
1
+ # Delivery record — one task
2
+
3
+ Use the relevant fields for a PR or private review. Sensitive artifacts stay private and are linked only from an authorized destination.
4
+
5
+ - User need, issue and accepted scope:
6
+ - Relevant design/contracts for a substantial change:
7
+ - Base commit, delivered commit and actually tested revision:
8
+ - Harness/model/environment versions and job/log reference:
9
+
10
+ | Slice and observable behavior | Before/after check, artifact and revision | Status and next step |
11
+ | --- | --- | --- |
12
+ | Complete relevant rows | Label mocks, missing baselines and live integrations | Passed / failed / blocked |
13
+
14
+ - Required checks, command/check ID, exit/result and evidence:
15
+ - Separate reviewer, revision and disposition:
16
+ - Change risk, security checks, findings and fix verification, or justified not applicable:
17
+ - Remaining uncertainties, recovery and next action:
18
+ - Delivery state: local / PR / accepted / merged / deployed / observed. Report only completed actions.
19
+
20
+ | Measurement | Value and coverage |
21
+ | --- | --- |
22
+ | Quality | Accepted / changes / failed / blocked / open; who assessed what? |
23
+ | Total direct cost | Inference, review, compute and every attempt; measured / estimated / unknown |
24
+ | All human time | Scoping/design, assistance, review and operations; identify missing coverage |
25
+ | Elapsed time | Start/end and active time; unfinished work has no final time-to-acceptance |
26
+ | Defects after acceptance | Regression, revision and observation window; not yet observed is valid |
27
+
28
+ Green checks on another revision or a model score cannot replace review of the delivered change. Merge and deployment follow the separate installation authority.
@@ -0,0 +1,32 @@
1
+ # Optional hosted-runner starting point; consumes private hosted minutes if enabled.
2
+ # For a qualified dedicated self-hosted environment, choose its labels instead.
3
+ # Never direct untrusted PR code to a personal or privileged persistent runner.
4
+ # Not installed or activated by the kit.
5
+ # Adapt the app's existing CI first. The app owns scripts/factory-ci, including
6
+ # dependency setup and its REAL checks. Missing setup must fail, never pass.
7
+ name: Factory checks
8
+ on:
9
+ pull_request:
10
+ workflow_dispatch:
11
+ permissions:
12
+ contents: read
13
+ concurrency:
14
+ group: factory-checks-${{ github.event.pull_request.number || github.ref }}
15
+ cancel-in-progress: true
16
+ jobs:
17
+ checks:
18
+ runs-on: ubuntu-latest
19
+ timeout-minutes: 15
20
+ steps:
21
+ # actions/checkout v6 tag resolved on 2026-09-24; review before upgrades.
22
+ - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803
23
+ with:
24
+ persist-credentials: false
25
+ - name: Run this app's configured checks
26
+ shell: bash
27
+ run: |
28
+ if [ ! -x ./scripts/factory-ci ]; then
29
+ echo 'Configure executable scripts/factory-ci with the real app checks before enabling this workflow.' >&2
30
+ exit 1
31
+ fi
32
+ ./scripts/factory-ci
@@ -0,0 +1,40 @@
1
+ # Application installation record
2
+
3
+ **Status: not configured.** Fill this from the repository and accepted owner choices. This document is not machine-enforced configuration. Store no secrets. Missing information blocks only the capability that needs it.
4
+
5
+ | Choice | Project answer and evidence |
6
+ | --- | --- |
7
+ | Repository, owner and main branch | Unselected |
8
+ | Existing instructions, product brief, architecture and non-goals | Reference existing sources |
9
+ | Observable user/API behavior and design source | Name representative checks and fixtures |
10
+ | Control surface | Existing issue/check/PR workflow unless otherwise selected |
11
+ | Harness, version, model/provider and billing | Unselected |
12
+ | Execution environment and necessary tools | Files, Git, shell and tests; browser for UI; other tools as needed |
13
+ | Start, stop, routing owner and recovery | Manual until automation is qualified; one owner per job |
14
+ | Authority and allowed network/credential references | Identify permissions without key values |
15
+ | Time, concurrency, attempts and actual provider stop limits | Unknown until exercised |
16
+ | Setup, dependencies, toolchain and test data | Exact commands and versions |
17
+ | Required checks and tested revision | Real commands/check identifiers |
18
+ | CI runner, allowed code, caches and artifact retention | Unselected |
19
+ | Security, risk context and private finding channel | Data, exposure, critical paths, recovery and relevant controls |
20
+ | Branch/PR/review rules | One writer; established review and publication authority |
21
+ | Deployment, rollback and post-release verification | No deployment unless configured and authorized |
22
+ | Data destinations, regions, private logs and backups | Record allowed locations and retention |
23
+ | Cost, human time and quality measurements | Record source and unknown coverage |
24
+
25
+ ## Qualify the selected combination
26
+
27
+ Record date, revision, result and artifact for each relevant check:
28
+
29
+ - The harness can discover the selected instructions and use the required tools.
30
+ - A real bounded app task and its meaningful regression checks work.
31
+ - Failed/missing checks and stale evidence cannot produce acceptance.
32
+ - A representative deliberate failure is detected while the baseline passes.
33
+ - The agent cannot read controller/admin credentials or private evaluator inputs.
34
+ - Separate review and any authorized PR workflow function.
35
+ - The chosen cloud job can be revisited after closing its client, if applicable.
36
+ - An automated trigger produces one job; duplicates, cancellation and restart are handled.
37
+ - Actual spend/resource stops work before unattended paid use.
38
+ - Release and rollback work in the selected test environment, if connected.
39
+
40
+ Distinguish synthetic fixtures, live integrations and actual model runs. Qualification covers the tested repository/harness/model/environment combination; material changes require relevant requalification. Do not claim unattended operation from configuration alone.
package/kit/policy.md ADDED
@@ -0,0 +1,41 @@
1
+ # Factory policy
2
+
3
+ The project instructions and the user's existing authority govern the work. This method does not grant credentials or publication rights. A concrete installation must enforce access, concurrency, resource limits, checks and release rules.
4
+
5
+ ## Delivery
6
+
7
+ 1. Scope the user need, observable outcome, allowed changes and verification. For substantial work, capture the relevant architecture/contracts and first runnable slice. Reuse already accepted decisions.
8
+ 2. Implement one observable vertical slice through the necessary layers, then exercise its behavior and meaningful failure cases before extending it. Preserve revision, evidence and next steps. Continue through the accepted scope without a new approval per slice; one completed slice is not acceptance of the entire job.
9
+ 3. Obtain a separate review of the exact delivered revision and required checks. Use the [delivery record](delivery.md). Local completion, PR, merge, deployment and post-release observation are separate statuses and authorities.
10
+
11
+ One writer owns each workspace. Worktrees alone do not isolate accounts, ports or databases. Reconcile unknown workers before replacements, retain evidence before cleanup, and keep acceptance criteria and evaluator resources outside the implementer's control. An agent report is not independent verification.
12
+
13
+ ## CI and CD
14
+
15
+ Use the application's existing reproducible checks, language versions and release pipeline. Relevant test-first work is useful when it captures a real behavioral regression; tests alone do not replace exercising the integrated path.
16
+
17
+ CI validates a known revision. Agent dispatch requires an authorized actor, accepted scope and capacity. Deployment follows explicit environment/release rules with separate credentials and recovery. Post-release verification measures actual delivery effects; ongoing incidents belong to the selected operational owner.
18
+
19
+ With GitHub Actions, use minimal permissions, reviewed commit-pinned actions, timeouts and concurrency. Untrusted PR code receives no deploy/model credentials and runs in an appropriate isolated environment. Do not run it through privileged pull_request_target or assume a persistent self-hosted runner is safe. Service-side branch and environment protections must actually be configured; documentation does not activate them. Avoid spending hosted minutes solely waiting for a long agent run.
20
+
21
+ ## Security and relative risk
22
+
23
+ Assess consequences, exposure, affected data/users, detectability, recovery and uncertainty. A small diff or solo project does not imply low risk. Assess at intake and again against the delivered change. Unknown risk stays unknown and newer revisions require relevant refreshed checks.
24
+
25
+ - At adoption, identify sensitive data, access, dependencies, trust boundaries and a private finding channel.
26
+ - For every change, run the agreed relevant checks. Tool failure or skipped checks do not constitute a clean scan.
27
+ - Changes to authorization, payment, sensitive data, network input, CI/agent policy or trust boundaries need appropriate scoped security review and reproduction where a credible finding exists.
28
+ - Validate concrete findings or relevant advisories, fix within authority and demonstrate vulnerable-before/fixed-after behavior. Keep raw findings private.
29
+ - Select broader release/periodic scans according to exposure, change and budget. Active external or production testing needs explicit bounded authority.
30
+
31
+ Available security specialists may help, but no plugin or model access is bundled by a skill. A model name, numeric judge score or successful scan cannot prove security.
32
+
33
+ ## Defence and operations
34
+
35
+ The optional defence workflow may share the installation/dashboard while retaining separate scope and authority. Incident investigation can cover software developed elsewhere. Convert a validated finding into a bounded software task with system/revision, impact, evidence and acceptance criteria. Software checks the repair; deployment and recovery verification follow their own policy. Shared UI grants no new production access.
36
+
37
+ ## Measurements
38
+
39
+ Record acceptance/quality, total direct cost, all human time, elapsed time and observed defects after acceptance. Retain failed attempts and repair/review/selection cost. Unknown is null, not zero; distinguish estimates from measurements. Model/skill comparisons require matched tasks, frozen versions, retained input digests and independent evidence. A synthetic fixture or token-throughput result is not business ROI.
40
+
41
+ Format/service references: [Agent Skills](https://agentskills.io/specification), [GitHub Actions security](https://docs.github.com/en/actions/reference/security/secure-use), [deployment environments](https://docs.github.com/en/actions/reference/workflows-and-actions/deployments-and-environments). These document formats and service behavior, not this installation's configuration.
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "software-defence-factory",
3
+ "version": "0.3.0",
4
+ "description": "Scoped software delivery and defence investigations with isolated jobs, evidence and review",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "engines": {
8
+ "node": ">=22.13.0"
9
+ },
10
+ "bin": {
11
+ "software-defence-factory": "bin/software-defence-factory.mjs"
12
+ },
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "https://github.com/arcitai/software-and-defence-factory.git"
16
+ },
17
+ "homepage": "https://github.com/arcitai/software-and-defence-factory#readme",
18
+ "publishConfig": {
19
+ "access": "public",
20
+ "registry": "https://registry.npmjs.org/"
21
+ },
22
+ "files": [
23
+ "bin/",
24
+ "factory/",
25
+ "kit/",
26
+ ".agents/skills/",
27
+ ".github/ISSUE_TEMPLATE/factory-task.yml",
28
+ "config/labels.json",
29
+ "scripts/export-kit.mjs",
30
+ "scripts/probe-platform.mjs",
31
+ "docs/npm.md",
32
+ "docs/quickstart.md",
33
+ "docs/defence-integration.md",
34
+ "docs/ownership.md",
35
+ "THIRD_PARTY_NOTICES.md"
36
+ ],
37
+ "scripts": {
38
+ "factory": "node bin/software-defence-factory.mjs",
39
+ "probe:platform": "node scripts/probe-platform.mjs",
40
+ "test": "node --test tests/*.test.mjs",
41
+ "check": "node scripts/check.mjs && node --test tests/*.test.mjs && npm run test:dashboard",
42
+ "build:dashboard": "npm --prefix dashboard ci --ignore-scripts && npm --prefix dashboard run build",
43
+ "test:dashboard": "npm --prefix dashboard test"
44
+ }
45
+ }
@@ -0,0 +1,65 @@
1
+ import { mkdirSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs";
2
+ import { dirname, join, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ import { execFileSync } from "node:child_process";
5
+ import { createHash } from "node:crypto";
6
+
7
+ const root = fileURLToPath(new URL("../", import.meta.url));
8
+ const args = process.argv.slice(2);
9
+ const usage = "Usage: node scripts/export-kit.mjs NEW_OUTPUT_DIRECTORY";
10
+ if (args.length === 1 && args[0] === "--help") {
11
+ console.log(`${usage}\nExports a reviewable adoption kit. Never installs into an app or overwrites a directory.`);
12
+ } else if (args.length !== 1 || args[0].startsWith("-")) {
13
+ console.error(usage);
14
+ process.exitCode = 1;
15
+ } else {
16
+ let created = false;
17
+ const destination = resolve(args[0]);
18
+ try {
19
+ const sources = new Map();
20
+ for (const name of ["README.md", "policy.md", "installation.md", "delivery.md", "examples/github-checks.yml.example"])
21
+ sources.set(`.factory-kit/${name}`, `kit/${name}`);
22
+ for (const role of ["triage", "spec", "implement", "review", "security", "evaluate"]) {
23
+ const path = `.agents/skills/factory-${role}/SKILL.md`;
24
+ sources.set(path, path);
25
+ }
26
+ sources.set(".factory-kit/LICENSE", "LICENSE");
27
+ sources.set(".factory-kit/labels.json", "config/labels.json");
28
+ sources.set(".github/ISSUE_TEMPLATE/factory-task.yml", ".github/ISSUE_TEMPLATE/factory-task.yml");
29
+ // Read only the public kit allowlist, before creating anything at destination.
30
+ const files = new Map([...sources].map(([target, source]) => [target, readFileSync(join(root, source))]));
31
+ files.set("START-HERE.md", Buffer.from("# Software & Defence Factory kit\n\nRead [the adoption guide](.factory-kit/README.md). The kit folders start with a dot and may be hidden in your file browser. This is a staged package; no app, workflow or service has been configured.\n"));
32
+ let sourceRevision = null, sourceDirty = null;
33
+ try {
34
+ const git = (args) => execFileSync("git", args, { cwd: root, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
35
+ // A ZIP unpacked inside another repository must not inherit its identity.
36
+ if (realpathSync(git(["rev-parse", "--show-toplevel"])) === realpathSync(root)) {
37
+ sourceRevision = git(["rev-parse", "HEAD"]);
38
+ sourceDirty = git(["status", "--porcelain"]).length > 0;
39
+ }
40
+ } catch {
41
+ // A downloaded source ZIP may have no Git metadata; file hashes still apply.
42
+ }
43
+ const manifest = {
44
+ format: 1,
45
+ sourceRevision,
46
+ sourceDirty,
47
+ hashBase: "export root",
48
+ files: [...files].map(([path, bytes]) => ({ path, sha256: createHash("sha256").update(bytes).digest("hex") })),
49
+ };
50
+ files.set(".factory-kit/manifest.json", Buffer.from(`${JSON.stringify(manifest, null, 2)}\n`));
51
+ // Non-recursive creation refuses any existing directory/file/symlink.
52
+ mkdirSync(destination);
53
+ created = true;
54
+ for (const [path, bytes] of files) {
55
+ const target = join(destination, path);
56
+ mkdirSync(dirname(target), { recursive: true });
57
+ writeFileSync(target, bytes, { flag: "wx" });
58
+ }
59
+ console.log(`Exported ${files.size} files to ${destination}. Review .factory-kit/README.md before adoption. No app or service was configured.`);
60
+ } catch (error) {
61
+ if (created) rmSync(destination, { recursive: true, force: true });
62
+ console.error(`Kit export failed: ${error.message}`);
63
+ process.exitCode = 1;
64
+ }
65
+ }
@@ -0,0 +1,75 @@
1
+ // Explicit, opt-in integration qualification. Uses Docker and the native controller, never inference.
2
+ import assert from 'node:assert/strict';
3
+ import { readFileSync, writeFileSync, existsSync } from 'node:fs';
4
+ import { resolve, join } from 'node:path';
5
+ import { api, configAt, save, json, containers, sleep, stream, ROOT } from '../factory/lib.mjs';
6
+ import { admitIncident } from '../factory/incident.mjs';
7
+
8
+ const state=resolve(process.argv[2] || '.factory/demo-platform'),original=configAt(state);
9
+ assert.equal(original.agent,'mock','Qualification is restricted to a synthetic installation');
10
+ assert.equal(readFileSync(join(original.repo,'value.txt'),'utf8'),'broken\n');
11
+ assert(!(await api(state,'/api/v1/status')).jobs.some(j=>['queued','running','awaiting_approval'].includes(j.state)),'Finish/cancel active demo jobs before qualification');
12
+ const results=[];
13
+ const cli=(...args)=>stream(process.execPath,[join(ROOT,'bin/software-defence-factory.mjs'),...args,'--state',state]);
14
+ const snapshot=async id=>(await api(state,'/api/v1/status')).jobs.find(j=>j.id===id);
15
+ async function until(fn,ms=45000) {
16
+ const deadline=Date.now()+ms;
17
+ while(Date.now()<deadline){const value=await fn();if(value)return value;await sleep(250);}
18
+ throw new Error('Timed out waiting for the required state');
19
+ }
20
+ const waitState=(id,wanted)=>until(async()=>{const job=await snapshot(id);if(job.state===wanted)return job;if(['failed','blocked','cancelled','interrupted'].includes(job.state)&&job.state!==wanted)throw new Error(`${id}: expected ${wanted}, got ${job.state}: ${job.runs.at(-1).error}`);});
21
+ const submit=(workflow,spec)=>api(state,'/api/v1/jobs',{workflow,repository:'app',spec,title:`Qualification: ${spec.slice(0,60)}`});
22
+ const work=spec=>submit('software',spec);
23
+ const record=name=>{results.push({name,passed:true});console.log(`PASS ${name}`);};
24
+ const setConfig=changes=>save(join(state,'factory.json'),{...original,...changes});
25
+
26
+ try {
27
+ const pass=await work('Synthetic complete vertical slice');await waitState(pass.id,'awaiting_approval');
28
+ const folder=join(state,'jobs',pass.id),candidate=json(join(folder,'candidate.json'));
29
+ assert.equal(json(join(folder,'checks.json')).head,candidate.head);assert.equal(json(join(folder,'review.json')).head,candidate.head);
30
+ assert.equal(readFileSync(join(folder,'checkout/value.txt'),'utf8'),'fixed\n');
31
+ assert.equal(readFileSync(join(original.repo,'value.txt'),'utf8'),'broken\n');
32
+ await cli('approve',pass.id);await waitState(pass.id,'succeeded');
33
+ assert.equal(json(join(folder,'accepted.json')).head,candidate.head);record('software: exact revision, isolated checkout, checks, review, approval, handoff');
34
+
35
+ const changed=await work('Synthetic changed revision guard');await waitState(changed.id,'awaiting_approval');
36
+ writeFileSync(join(state,'jobs',changed.id,'checkout/value.txt'),'changed after review\n');
37
+ await cli('approve',changed.id);await waitState(changed.id,'failed');
38
+ assert(!existsSync(join(state,'jobs',changed.id,'accepted.json')));record('changed candidate cannot inherit earlier approval');
39
+
40
+ const policy=await work('Synthetic policy change guard');await waitState(policy.id,'awaiting_approval');
41
+ setConfig({check:'true'});await cli('approve',policy.id);await waitState(policy.id,'failed');
42
+ assert(!existsSync(join(state,'jobs',policy.id,'accepted.json')));setConfig({});record('changed check policy cannot inherit earlier approval');
43
+
44
+ setConfig({check:'exit 17'});
45
+ const fail=await work('Synthetic failing check');await waitState(fail.id,'failed');
46
+ assert.equal((await snapshot(fail.id)).runs.at(-1).command,'verify');
47
+ setConfig({});await cli('retry',fail.id);await waitState(fail.id,'awaiting_approval');
48
+ await cli('approve',fail.id);await waitState(fail.id,'succeeded');record('failed app check blocks delivery; controlled retry rechecks same candidate');
49
+
50
+ const cancel=await work('SYNTHETIC_TIMEOUT cancellation fixture');
51
+ const live=await until(()=>containers(state).find(c=>c.Config.Labels['sdf.job']===cancel.id&&c.State.Running));
52
+ assert(live.HostConfig.ReadonlyRootfs);assert(live.HostConfig.CapDrop.includes('ALL'));assert.equal(live.HostConfig.NetworkMode,'none');
53
+ assert(!live.Mounts.some(m=>m.Source.includes('docker.sock')));assert(!live.Mounts.some(m=>m.Destination==='/workspace/.git'&&m.RW));
54
+ assert.notEqual(live.Config.User.split(':')[0],'0');
55
+ await cli('cancel',cancel.id);await waitState(cancel.id,'cancelled');
56
+ await until(()=>!containers(state).some(c=>c.Config.Labels['sdf.job']===cancel.id));record('non-root container boundaries; cancel confirms container stop');
57
+
58
+ setConfig({timeoutSeconds:2});const timeout=await work('SYNTHETIC_TIMEOUT deadline fixture');
59
+ await waitState(timeout.id,'failed');assert(!containers(state).some(c=>c.Config.Labels['sdf.job']===timeout.id));record('bounded deadline stops agent and fails the attempt');
60
+ setConfig({});
61
+
62
+ const input={...json(join(ROOT,'factory/examples/incident.json')),event_id:`probe-${Date.now()}`};
63
+ const first=await admitIncident(state,input,submit),again=await admitIncident(state,input,submit);
64
+ assert.equal(first.id,again.id);assert(again.deduplicated);await waitState(first.id,'succeeded');
65
+ const report=json(join(state,'jobs',first.id,'incident-report.json'));assert.equal(report.case_state,'open');assert.equal(report.verification,'not_performed');
66
+ assert.equal(report.production_action_taken,false);record('incident deduplication, private draft and no recovery claim');
67
+
68
+ const interrupted=await work('SYNTHETIC_TIMEOUT restart fixture');
69
+ await until(()=>containers(state).some(c=>c.Config.Labels['sdf.job']===interrupted.id&&c.State.Running));
70
+ await cli('stop');assert.equal(containers(state).length,0);await cli('up');await waitState(interrupted.id,'interrupted');
71
+ setConfig({timeoutSeconds:2});await cli('retry',interrupted.id);await waitState(interrupted.id,'failed');
72
+ assert.equal((await snapshot(interrupted.id)).runs.length,2);assert.equal(containers(state).length,0);record('stop/restart retains interrupted state; retry proves previous writer stopped');
73
+ save(join(state,'qualification.json'),{synthetic:true,platform:process.platform,arch:process.arch,engine:json(join(state,'engine.json')).version,results});
74
+ console.log(`Qualified ${results.length} paths. Model quality, cost and production connectors were not measured.`);
75
+ } finally {save(join(state,'factory.json'),original);}