Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion config/apps.json
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,8 @@
"level": "read",
"needs": [
"read:records.pass_record",
"read:records.runs"
"read:records.runs",
"read:records.artifacts"
]
},
"checks": {
Expand Down
40 changes: 33 additions & 7 deletions docs/architecture/software-factory-design.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ Made by the Maintainer on 2026-10-10, in addition to ADR-0013's twelve decisions
| The Factory's Apps share the prefix `combsmith-sf-` | §5.1 |
| Every panel of the control room is public | §8.6 |
| Code owners follow the org's standard: the team org-leads owns every path (2026-10-14) | §5.1 |
| A Finding that does not gate is an annotation on its Lens's check run and has no Disposition; a `security` Finding gates at any severity (2026-10-14) | §3.7, §4.2, §4.3 |
| Only required Lenses gate, and domain owners start in shadow (from the pre-mortem, §9.3) | §4.2, §5.2 |
| The Maintainer records continue or stop at two decision points | §9.2 |
| Decisions read GitHub only; the control room is written and never read back (from the simplification pass, §9.4) | §3.9 |
Expand Down Expand Up @@ -321,7 +322,7 @@ for contract tests, so one schema has three uses.
| `changed_paths` | Paths in that diff |
| `labels` | Names |
| `reviews` | Who submitted it (`by`: kind, name and whether that actor has write access, as a thread's opening actor), state (`approved`, `changes_requested`, `commented` or `dismissed`, as GitHub holds it now), commit, `content_id`, `submitted_at`; and, from the timeline, `dismissed_by`: who dismissed it, of what kind and with what access, and `dismissed_from`: what it was before (`approved`, `changes_requested` or `commented`). Both are null unless it was dismissed |
| `threads` | Thread id, path, line, when it was opened, resolved, outdated, opening actor and whether that actor has write access, resolving actor |
| `threads` | Thread id, path, line, when it was opened, resolved, outdated, opening actor and whether that actor has write access, resolving actor; and the Finding's `category` and `severity`. For a required Lens's thread both come from that Lens's own Finding record, in its Run's `result.json` (§3.10), joined by `surface.key`, never from the comment text, which anyone with write access can edit. A person's thread is `human`, with no severity; an Advisory reviewer's, which no Finding record names, has neither |
| `checks` | Name, App, conclusion as GitHub gives it (or the check's status while it has not concluded), completion time, annotation count |
| `alerts` | Tool, rule, alert number, state, severity, path |
| `queue` | Merge-queue membership |
Expand Down Expand Up @@ -399,6 +400,13 @@ readable only in the one format, because times compare as text. The item is the
see, as for a check with no verdict: a port that misreads does not mend itself. A request made
after the item entered the merge queue takes it out of the queue (§4.1).

A required Lens's open thread whose `category` or `severity` is missing, null, or a word the Finding
schema does not name is unreadable in the same way. Whether it gates is read from both, and a
`security` Finding gates at any severity (§4.2), so it could be one: the Kernel defers, reason
`thread_unreadable`, with the thread's id as evidence, and the item is the Maintainer's to see. It
is read just after the reviews, and on a queued item it is a stop. A thread from a Lens that is not
required gates nothing, and may carry neither.

An issue has facts of its own (§4.1, the issue Lifecycle):

| Field | Content |
Expand Down Expand Up @@ -559,6 +567,13 @@ There is no Effect that starts a Run. Only Admission does that.
- `category` is one of `functional`, `security`, `architecture`, `delivery`, `reliability`,
`requirements`, `mechanical`, `uncategorised`, or `human`.
- `severity` is `blocking`, `major`, `minor` or `info`.
- Whether a required model Lens's Finding gates is read from its category and severity (§4.2). A
`security` Finding gates at any severity. One that does not gate, a `minor` or `info` Finding of
any other category, is reported as an annotation on its Lens's check run (`surface.type`
`annotation`), never as a thread, and has no Disposition (§4.3). The Harness refuses a Result
that posts one on a thread.
- A thread's category and severity in the facts are read from this record, joined to the thread by
`surface.key`, never from the comment text: anyone with write access can edit a comment (§3.3).
- Category and severity are always fields. A deterministic tool supplies them. A Role supplies them
for its own Findings. A third-party reviewer that writes them only as prose gets them by
**adoption**: `pr-review` is given that reviewer's Finding as input and, if it agrees, raises a
Expand Down Expand Up @@ -1239,14 +1254,19 @@ Convergence is one Kernel function of facts and run records:
on the current `content_id`. For a deterministic Lens, only `success` passes; a check that gave
no verdict, `neutral`, `skipped` or `cancelled` among them, is unreadable and the Kernel defers
(§3.3);
2. no Finding of severity `blocking` or `major` raised by a required Lens is open or proposed;
2. no Finding raised on a thread by a required model Lens is open or proposed that is of severity
`blocking` or `major`, or of category `security` at any severity. A required deterministic Lens
gates through its check's conclusion (item 1), so what it fails on is its tool's threshold;
3. no `human` Finding is open. A `human` Finding is a review thread, or a review that requests
changes, from a person with write access;
4. where the item has an acceptance contract, every criterion has passing evidence on this
content (§4.8).

A *blocking Finding*, here and in the diagram above, is one that gates: severity `blocking` or
`major`, from a required Lens or a human.
`major`, or category `security` at any severity, from a required model Lens; a required
deterministic Lens's failing check; or a `human` Finding. Severity is never how a security defect
is merged. A required model Lens's Finding that does not gate is an annotation on its check run,
and needs no Disposition before its item merges (§4.3, FR-33).

Which Lenses are required is data. The starting set is the deterministic checks, `pr-review` and
humans. A Lens joins it only with a measured contribution (ADR-0013, decision 12). A dependency
Expand Down Expand Up @@ -1315,9 +1335,14 @@ sequenceDiagram
Run's artifact and compared with the owner's. They move nothing.
- A rejected proposal returns the item to `addressing`. After `max_rounds` the item is placed on
Hold with the open Findings listed.
- A `security` Finding cannot be accepted as `declined` or `deferred` by any Role. Only an Override
by the Maintainer does that.
- A `security` Finding gates at any severity. It cannot be accepted as `declined` or `deferred` by
any Role. Only an Override by the Maintainer does that.
- A `mechanical` Finding is never judged by a model. It closes when its tool stops reporting it.
- A required model Lens's Finding that does not gate, a `minor` or `info` one of any category but
`security`, is never judged either. It is an annotation on its Lens's check run, never a thread, so no ruleset's
requirement that threads be resolved can hold its item. It has no Disposition, and closes when
its Lens stops reporting it, as a `mechanical` Finding does. Its Lens's report is its one
outcome, and FR-69 counts it. The Harness refuses a Result that posts one on a thread.
- A `human` Finding closes only when that person resolves it.

### 4.4 Invariants
Expand All @@ -1340,7 +1365,7 @@ These are the properties the simulator asserts. Each is a sentence a test can fa
| I-12 | Every recorded Transition is declared in the Lifecycle | FR-1 |
| I-13 | An unreadable fact or a missing capacity reading never yields an admission, nor any act except to take a pull request out of the merge queue | FR-35, C-1 |
| I-14 | Every cycle in the Lifecycle passes through a bounded counter or a human transition | FR-18, FR-57 |
| I-15 | Each Finding has exactly one owner and one Disposition per content version | FR-33 |
| I-15 | Each Finding has exactly one owner, and each Finding that gates one Disposition per content version | FR-33 |
| I-16 | A `security` Finding is never closed as declined without an Override record | Maintainer, 2026-10-10 |
| I-17 | The status on an item changes only when the decision changes | ADR-0013, decision 2 |
| I-18 | A Run that ends with no Result counts as an attempt unless its fault is classed as capacity or environment, and its item is re-evaluated | ADR-0013, decision 4 |
Expand Down Expand Up @@ -2367,7 +2392,8 @@ Found by explaining the design on one page and by comparing it with the PRD grou
|---|---|
| FR-1, for issues | Designed in §4.1 on 2026-10-11. Not yet declared as data, nor checked for termination. The Kernel has no facts or derivation for an issue yet. Left open there: which Role's App opens the draft pull request; the Consumer entry's fields for issues; the promotion record of an Intent that owns no category; and the PRD's own text on triage and arming (§10) |
| FR-6, resumable Runs | Not designed. A Run that times out pushes nothing and starts again |
| FR-33 and I-15, for a Finding that does not gate | Not designed. Convergence reads only `blocking` and `major` Findings (§4.2), so a `minor` or `info` Finding can be open, with no Disposition, when its item merges. Whether such a Finding is given a Disposition before the merge, or closed as deferred by it, is the Maintainer's to rule on |
| FR-33 and I-15, for a Finding that does not gate | Designed in §4.2 and §4.3 on 2026-10-14, by the Maintainer's ruling. A required model Lens's `minor` or `info` Finding of any category but `security` is an annotation on its Lens's check run, has no Disposition, and closes when its Lens stops reporting it; a `security` Finding gates at any severity. The Kernel holds the gate. Refusing such a Finding when it is posted on a thread is waiting on `harness/` |
| A `security` Finding from a deterministic Lens, at any severity | Not held. A deterministic Lens gates through its check's conclusion, and code scanning's default fails the check only on a security alert of `high` or higher. Its threshold must be set to fail on a security alert of any severity: issue #95 "Code scanning fails on a security alert of any severity" |
| FR-12, the injection test suite | Required before promotion; not designed |
| FR-14, shared-branch safety | The rule is stated; the push guard is to be carried over from the POC |
| FR-21, FR-36, priority and graceful degradation | Backpressure and the capacity outcome exist. The stages between full service and Pause are not designed |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,22 +9,36 @@ Every source of judgement on a Work item, whether a Role, a third-party reviewer
person, is a Lens that reports Findings in one schema. Convergence is a Kernel function: every
required Lens has passed on the current content, and no gating Finding is open. Lenses do not
address each other. A Finding from a Lens outside the required set gates only when a required Lens
adopts it. A reviewing Role declares a rubric, which is a floor. A change that has not converged
within its bound is placed on Hold.
adopts it. A required model Lens's Finding gates when it is `blocking` or `major`, and a `security`
one at any severity; a required deterministic Lens gates through its check's conclusion. One that
does not gate, a `minor` or `info` Finding of any category but `security`, is an annotation on its
Lens's check run and has no Disposition: it closes when its Lens stops reporting it, as a
`mechanical` Finding does (Maintainer, 2026-10-14). A reviewing Role declares a rubric, which is a floor. A change that has
not converged within its bound is placed on Hold.

## Why

A council of independent Lenses finds more than one reviewer, but only if they stay independent
and something other than their conversation decides. The proof of concept decided by parsing
comments, and waited for reviewers that never answered.

A Disposition for every small Finding would cost Runs and bring back the gate built on every
comment, and closing one as deferred at the merge cannot work: the ruleset requires resolved
threads, so an open one would hold a merge the Kernel approved. An annotation's report still gives
each Finding one outcome, which is counted. Severity is never how a security defect gets merged.

## Boundaries

| Boundary | Check |
|---|---|
| A Finding is a structured record | `schemas/finding.schema.json`, `tools/check_examples.py` |
| A reason is a code, never a sentence | `examples/invalid/decision.sentence-as-reason.json` |
| A required Lens's blocking or major Finding gates; a minor one, or one from a Lens that is not required, does not | `tests/test_decide.py::test_required_lens_major_finding_gates`, `tests/test_decide.py::test_minor_finding_and_unrequired_lens_do_not_gate` |
| A required Lens's blocking or major Finding gates; a minor one of any category but `security`, or one from a Lens that is not required, does not | `tests/test_decide.py::test_required_lens_major_finding_gates`, `tests/test_decide.py::test_minor_finding_and_unrequired_lens_do_not_gate` |
| A required Lens's `security` Finding gates at any severity, in the merge queue too; one from a Lens that is not required gates only once a required Lens adopts it | `tests/test_decide.py::test_a_minor_security_finding_gates`, `tests/test_decide.py::test_a_minor_security_finding_opened_while_queued_takes_it_out_of_the_queue`, `tests/test_decide.py::test_a_security_finding_from_a_lens_that_is_not_required_does_not_gate` |
| A required Lens's open thread whose category or severity is missing, null or not in the schema defers for the Maintainer, and takes a queued item out; a thread from a Lens that is not required may carry neither | `tests/test_decide.py::test_a_required_lens_thread_that_cannot_be_read_defers`, `tests/test_decide.py::test_a_read_that_fails_takes_a_pull_request_out_of_the_queue` |
| A thread's category and severity are read from the required Lens's own Finding record, joined by its surface key, never from the comment text | waiting on `adapters/` |
| A thread carries its Finding's category, in the authority table's terms, and the Kernel reads the Finding's categories and severities | `tests/test_tools.py::test_the_kernel_reads_the_categories_and_severities_a_finding_carries`, `examples/invalid/facts.a-thread-without-its-category.json`, `examples/invalid/facts.a-category-nobody-owns.json`, `tests/test_tools.py::test_a_thread_carries_the_categories_a_finding_does` |
| A Finding that does not gate is an annotation on its Lens's check run; a Result that posts one on a thread is refused | waiting on `harness/` |
| A required deterministic Lens is named for the check GitHub reports, and that check reports on every head and in the merge queue | waiting on `adapters/` |
| No model Lens runs on content the deterministic Lenses have not passed | `tests/test_decide.py::test_no_model_review_before_deterministic_checks_pass` |
| Only `success` passes a required check. `failure` and `timed_out` go back to the author, and are read first; queued, running, waiting and absent checks are waited for; a check that gave no verdict, or whose conclusion the map does not name, defers and needs the Maintainer | `tests/test_decide.py::test_a_neutral_required_check_defers`, `tests/test_decide.py::test_a_timed_out_required_check_goes_back_to_the_author`, `tests/test_decide.py::test_a_queued_running_or_absent_required_check_is_waited_for`, `tests/test_decide.py::test_a_failed_check_is_read_before_an_unreadable_or_waiting_one`, `tests/test_decide.py::test_a_failure_with_cancelled_or_skipped_siblings_goes_back_to_the_author` |
Expand Down
11 changes: 10 additions & 1 deletion docs/prd/agentic-software-factory.md
Original file line number Diff line number Diff line change
Expand Up @@ -473,9 +473,18 @@ each withhold or dismiss the single approval the ruleset needs. Realizes UJ-1, U
- A Finding from an Advisory reviewer outside the required Council gates only when the in-house
reviewer adopts it as its own. The in-house reviewer resolves each thread it does not adopt, so
that a platform rule requiring resolved threads stays in force.
- **FR-33. One Disposition per Finding per content version.** Every Finding from any Lens is
- **FR-33. One Disposition per gating Finding per content version.** Every Finding that gates is
dispositioned exactly once for a given content version. The Gates read the outcome from the
Finding's surface: a thread, and which App resolved it.
- A Finding a required model Lens raises on a thread gates when it is `blocking` or `major`, or
when it is a `security` Finding, at any severity. Severity is never how a security defect is
merged. A required deterministic Lens gates through its check's conclusion, so what it fails on
is its tool's threshold: code scanning's must fail on a security alert of any severity (issue
#95 "Code scanning fails on a security alert of any severity"). A `human` Finding always gates.
- A required model Lens's Finding that does not gate, a `minor` or `info` one of any category but
`security`, is given no Disposition. It is an annotation on its Lens's check run, never a
thread, and closes when the Lens stops reporting it, as a `mechanical` Finding does. Its Lens's
report still gives it one outcome, which FR-69 counts. Decided by the Maintainer on 2026-10-14.
- A Finding's identity is a structured key from its surface (a thread, an alert, an annotation),
never text matched in a body. An edited summary comment is therefore not a new Finding.
- A review thread, or a review that requests changes, from a person with write access is a
Expand Down
2 changes: 2 additions & 0 deletions examples/facts.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"name": "pr-review",
"can_write": true
},
"category": "functional",
"severity": "major",
"resolved_by": null,
"opened_at": "2026-10-11T12:15:00Z"
Expand All @@ -75,6 +76,7 @@
"name": "coderabbit",
"can_write": false
},
"category": null,
"severity": null,
"resolved_by": null,
"opened_at": "2026-10-11T12:15:00Z"
Expand Down
Loading
Loading