feat(software-factory): add @swamp/software-factory extension #58
Loading…
Reference in a new issue
No description provided.
Delete branch "software-factory"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Adds the
@swamp/software-factoryextension: a generic, model-driven statemachine for guiding agents through a software development lifecycle. Stages,
transitions, gates, required artifacts, review skills, and prompts all live in
the definition's
globalArgumentsas data — the engine ships no lifecycleconcepts (adversarial review, testing, release, UAT are expressible, never
assumed; see
examples/).Ported from the standalone software-factory repo to match the structure of the
existing extensions (model + skill + report, cf.
issue-lifecycle):@adam->@swampthroughout (manifest, modelcode, report type names, skill docs, examples).
manifest.yamlrepository at this repo; normalised.swamp.yamltothe
tool: claudeform; adopted the skill-tracking.gitignorepattern.software-factoryskill, model engine +_lib, thework-item-summary report, and three example factory definitions.
CI
The CI workflow enumerates extensions statically, so this PR also wires
software-factoryinto.forgejo/workflows/ci.yml, mirroring thecvepattern (models + reports): change-detection filter, check/lint/fmt/test
matrix, lockfile verification, deps-audit directory, and the new jobs added to
the claude-review, claude-adversarial-review (incl. its trigger condition), and
merge-gate dependency lists. Publishing is already generic (
publish.ymldiffs
*/manifest.yaml), so no change was needed there.Verification:
deno task check/lint/fmt:checkclean,deno install --frozenpasses, 143 tests pass / 0 failures. The exact CIcommands were run locally and pass.
🤖 Generated with Claude Code
CI Security Review
Summary
This PR adds CI check and lockfile verification jobs for the new
software-factoryextension, following the identical pattern used by all other extensions (cve, kubernetes, workflows, etc.). The changes are:software-factoryoutput to thechangesjob (lines 30, 68)software-factory-checkandsoftware-factory-lockfile(lines 406–449) — standard deno check/lint/fmt/test matrixsoftware-factoryto thefindsearch paths (line 673)needs:arrays forclaude-review,claude-adversarial-review, andmerge-gatesoftware-factoryto the conditional that gates the adversarial review (line 870)Security Analysis
1. Prompt Injection — No new LLM prompts introduced. Only
needs:arrays updated on existing Claude review jobs. ✅2. Expression Injection —
${{ matrix.task }}is used inrun:blocks (lines 425–433), but the matrix values are hardcoded literals ([check, lint, fmt, test]) defined in the workflow YAML itself. Not attacker-controllable. Same pattern as all existing extension jobs. ✅3. Dangerous Triggers — No trigger changes. Workflow remains
pull_requestonly (notpull_request_target). ✅4. Supply Chain — New jobs use
actions/checkout@v6anddenoland/setup-deno@v2, both trusted publishers acceptable with tag-only pins per policy. No new third-party actions. ✅5. Permissions — No new permissions. New jobs inherit the workflow-level
contents: read. ✅6. Secret Exposure — New jobs use no secrets. ✅
7. Auto-merge & Trust Boundaries — No changes to merge logic. New jobs correctly added to the merge gate. ✅
Verdict
PASS — Security-neutral change. Boilerplate addition of CI jobs for a new extension, following established patterns with no new attack surface.
Code Review
Blocking Issues
None.
Suggestions
mermaid.tshas no test coverage. Every other_lib/file has a corresponding_test.ts, butmermaid.ts— which providesrenderMermaidandrenderTablesused by thedescribemethod — has none. The rendering logic is deterministic and straightforward to unit-test against a known fixture definition.CI test flags differ from
deno.json's test task. The CI workflow runs:while
deno.jsondefines:If
cel-js(imported live insoftware_factory_test.ts) requires--allow-sysat runtime,deno task testwill fail for those tests locally. Align the flags so the documented local command matches CI.buildCelContextingates.tssilently coalesces hyphen/underscore name collisions.celName()maps bothplan-reviewandplan_reviewto the same CEL keyplan_review. The graph validator (graph.ts) enforces global uniqueness of artifact names but compares the raw names (which are distinct), so a definition with both would pass validation yet produce a silently shadowed key in everycelgate evaluation. A guard invalidateGraph— or at least inbuildCelContext— would prevent this footgun.Unescaped interpolation in CEL predicates. Both
queryRunDataReader.versionsOfinsummary.tsandworkflowSucceededingates.tsbuild CEL predicate strings via template literals:In practice the values are swamp-managed (NameSchema-validated slugs, platform-generated workflow run IDs), so the risk is low. However, a workflow run ID containing an unescaped
"in a corrupted or adversarially-crafted record would break the predicate. Quoting/escaping the interpolated values, or validating them first, would be more defensive.Adversarial Review
Medium
gates.ts:347-356/graph.ts— CEL name collision allows silent artifact/evidence shadowing in gate evaluation.celName()(definition_schema.ts:400) maps-to_, so two artifacts with namesreview-planandreview_planboth becomereview_planin the CEL context. InbuildCelContext, the second map iteration silently overwrites the first:Graph validation (
graph.ts:75-83) checks for duplicate raw artifact names but not post-celNameduplicates. A CEL gate expression likeartifacts.review_plan.summary != ""would evaluate against whichever artifact happened to iterate last in the Map — producing silently wrong gate results. The same applies to evidence names (gates.ts:354-356) and approval gate IDs (gates.ts:359).Breaking input: A definition with artifacts
code-reviewandcode_reviewon different stages. A CEL gate referencingartifacts.code_reviewreads the wrong payload.Suggested fix: Add a celName-uniqueness check to
validateGraph— after collecting all artifact names, also build aSet<string>ofcelName(name)values and error on collision.artifact_schema.ts:55— ReDoS via user-controlled regex pattern in artifact schemas.The
patternfield from a factory definition's artifact schema is compiled into aRegExpwithout any complexity check. A pathological pattern like(a+)+$causes catastrophic backtracking when matching certain inputs. This runs both at definition validation time (compileArtifactSchemaviavalidateGraph) and at payload recording time (validateArtifactPayload). A crafted payload like"aaa...aab"against such a pattern would block the engine thread.Breaking input: An artifact declares
schema: { type: "string", pattern: "(a+)+$" }, thenrecord_artifactis called with payload containing"aaaaaaaaaaaaaaaaaaaaaaaaaaab".Suggested fix: Either impose a max pattern length or set a timeout/complexity limit. Alternatively, wrap
new RegExp(decl.pattern)in a try-catch at compilation time (it already is, invalidateGraph), and consider using a regex-safe library or at minimum documenting the operator trust boundary.Low
gates.ts:309—cooldowngate silently passes on corruptedrecordedAttimestamp.If
recordedAtis not a valid ISO date,new Date(recordedAt).getTime()returnsNaN, makingelapsedNaN. SinceNaN < anythingisfalse, the cooldown check is skipped and the gate passes unconditionally. This only matters with corrupted data (the engine always writesnew Date().toISOString()), but a cooldown gate is a safety control — failing open is the wrong default.Suggested fix: Add
if (Number.isNaN(elapsed)) return fail(gate, "invalid timestamp on ...")before the comparison.gates.ts:498-504— unescaped string interpolation in workflow-succeeded query predicates.summary.workflowRunIdis parsed from JSON content stored in the data repository. If that content contains a"character (corrupted or crafted data), it breaks the CEL predicate syntax or alters its semantics. Attack surface is limited to compromised platform data — not user-facing input.Suggested fix: Escape double-quotes in interpolated values, or use a parameterized query interface if the platform supports one.
software_factory.ts:1086-1176— non-atomic read-modify-write inresolve_findings.resolve_findingsreads the current artifact, mutates the findings array, and writes back. Two concurrent calls targeting the same artifact would each read the same version, apply their own resolutions, and write — the last write wins, silently dropping the other's resolutions. The model code does not use optimistic concurrency (e.g., compare-and-swap on version).Suggested fix: Either version-check before writing (fail if the version changed since read) or document that concurrent
resolve_findingscalls on the same artifact are unsupported.Verdict
PASS — The code is well-structured with thorough validation, defense-in-depth gate re-evaluation, and good error messages. The findings above are edge cases: the CEL name collision (#1) and ReDoS (#2) are the most worth addressing before this ships, but neither is exploitable from the normal driver/user path since definitions are operator-authored. No critical or high severity issues found.