Repository navigation
Commit ef5dae4
authored
Written-by: Dev3
Fixes #1290
Refs #1272
Owner: Dev3
Reviewer: Reviewer
Tip SHA: 8f1864f
Step 1 of `#1272`, the pure core, so the parent issue stays open: step 2
adds the `repo-dev` preset and the fact providers that mirror
`enqueue-guard.mjs` / `merge-gate.mjs`, step 3 adds the Workflows page.
`Fixes #1290` names the step-1 child issue for the same reason `#1275`
closed `#1279`: one issue, one pull request, and `#1272` is closed by
the last step.
## Reproduce
On the base of this PR, main `63f81f03`, there is no such file and no
such answer:
```
$ ls core/src/shared/workflow.js
ls: core/src/shared/workflow.js: No such file or directory
```
So nothing can say whether a node may start, and today's board has no
way to refuse a node whose input moved. The three confusions the issue
records (#1239's `Verdict: Incorrect` parsed as nothing, a verdict
naming an old tip, a `Tip SHA` line naming the previous tip) were each a
node started on a stale input that only a repo-side script noticed
afterwards. This PR is the function that would have answered:
`enterNode(def, 'gate', { present, current })` returns `ok: false` with
`Gate refuses: its input verdict@abc1234 is stale, the tip is now
def5678.`
## What changed
Three new pure modules, no host, no UI, no GitHub:
- `core/src/shared/artifact.js` — the artifact vocabulary
(`check:tests-ok`, `verdict`, `branch`, `pr`, `mergeable`,
`merge-commit`, `release`, `risk:tier`, `doc|file|link|text`), what the
revision of each kind is, and `freshness()`: present and current are two
questions, and a fact nobody could read is `unreadable` instead of a
match.
- `core/src/shared/workflow.js` — `validateWorkflow()` (an input nothing
produces, an output nothing consumes that is not a deliverable, two
producers of one fact, an undeclared cycle, a node with no Bot, a Bot
not on the team, an unknown artifact kind, an edge that does not line up
— each with its own sentence), `workflowColumns()` (the node ids are the
columns; no built-in list), `layoutWorkflow()` (one box per node with
its Bot and rails, one edge per `edges[]` row, ranks left to right,
computed not stored), `enterNode()` / `inputStates()` /
`renderRefusal()` (missing or stale, both revisions named, the node's
own `refusal` wording when it has one), `holderOf()` (the node's Bot is
the card's assignee).
- `core/src/shared/workflow-blank.js` — the `blank` preset as plain
data: three nodes, one Bot each, no guards, no repository. Nothing in
the engine names its ids, and a test renames them all to prove it.
`docs/subsystems/core-host.md` gains the three rows in the shared-module
table and one invariant under `## Invariants`. The four
`patches/ds-bot/files/` mirrors are the generated whole-file copies,
byte-identical to `core/`. No changelog fragment: the engine is not
reachable from any surface yet, so a user cannot see it; the Workflows
page in step 3 carries the entry (changelog.d/README: "A change that
users cannot see needs no fragment").
## What I ran
On the pushed tip `8f1864ff0c6f22843251c2c98137c873f6017bf9` (base
`63f81f03`, which is main when this was written):
```
$ node --import ./test/harness-resolve.mjs --test test/workflow.test.js
# tests 19
# pass 19
# fail 0
$ node --import ./test/harness-resolve.mjs --test test/workflow.test.js test/board-card.test.js test/kanban.test.js test/delivery-view.test.js
# tests 48
# pass 48
# fail 0
$ node scripts/ds-bot-patches.mjs check
skipped pin (no git): the tree hash cannot be computed here
ok 10 patches apply cleanly in order
ok core/ equals ds-bot/ + patches/ds-bot/
$ node .github/scripts/changelog.mjs check
changelog: 15 fragment(s) in changelog.d/ are well formed
$ node .github/scripts/check-mojibake.mjs --check
mojibake: ok — 0 occurrences of U+FFFD in 1119 text file(s)
```
The 19 tests cover: the artifact vocabulary and revisions, present vs
current, the `blank` preset against its team, a five-node content
workflow through the same calls (no repository anywhere), the flowchart
asserted against the definition (one box per node with its Bot and
rails, one edge per row, ranks in dependency order, the same layout
twice), each validation refusal with its own code and sentence, a
declared loop versus an undeclared cycle, the node's own refusal
wording, a guard over a verdict value, renamed ids still laying out the
same, and the three answers the INC at `38ef59de` asked for: an unread
source refuses whether the key is missing or `null`, the sentence names
the fact and never fills an empty slot, and the card reads `stale: true`
on the same input the gate refuses.
## The INC at 38ef59d
`Verdict: INC at 38ef59d` (Reviewer2,
comment `6414982598`) found one defect in three places: `current`
without a key was treated as "nothing to compare" and the node started.
Fixed here, one site each:
1. **`enterNode` no longer skips a missing `current` key**
(`core/src/shared/workflow.js:383`): every input goes through
`freshness()`, so `current: {}` and `current: { verdict: null }` both
answer `unreadable` and the node refuses, the same branch the `is:
'fresh'` guard already took. Test: *a fact whose source revision is
unreadable refuses, whether the key is missing or null*.
2. **`renderRefusal` says what it read instead of leaving a hole**
(`:302`): the unreadable case has its own sentence — the fact with its
revision, that the source revision could not be read, and that the card
must be read before the node starts — and `null` never reaches `{now}`
or `{have}` (the old path rendered `stale, the tip is now .`). Test:
*the unreadable sentence names the fact it read and never fills the
source revision with an empty slot*.
3. **`inputStates` no longer reports `stale: false` for an input the
gate refuses** (`:324`): it now returns `stale: !fresh` plus `reason`
and `unreadable`, so the card and `enterNode` say the same thing. Test:
*the card and the gate say the same thing about an unreadable input*.
## Red without the source change
Two knives, each cut on the pushed tree
`8f1864ff0c6f22843251c2c98137c873f6017bf9` and restored with `cp`:
```
$ # A: enterNode no longer compares the input's revision with the source's
not ok 14 - a stale input refuses with both revisions, so a card reads without asking anyone
not ok 17 - a fact whose source revision is unreadable refuses, whether the key is missing or null (#1291 INC)
not ok 18 - the unreadable sentence names the fact it read and never fills the source revision with an empty slot (#1291 INC)
not ok 19 - the card and the gate say the same thing about an unreadable input (#1291 INC)
# tests 19 / # pass 15 / # fail 4
$ # B: the cycle check is removed from validateWorkflow
not ok 11 - a cycle that is not declared is refused; a declared loop is how work goes back
# tests 19 / # pass 18 / # fail 1
```
Knife A now reds four rather than one because the three INC tests sit on
the same three lines; the narrow version of the same cut is the INC
defect itself (put the `hasOwnProperty … continue` back), which reds
exactly two: *not ok 17*, *not ok 19* (`# pass 17 / # fail 2`). The
other two sites have their own knives on the same tree: putting the old
`renderRefusal` back reds *not ok 17* and *not ok 18* (`# pass 17 / #
fail 2`), and restoring the old `inputStates` `stale` computation reds
*not ok 14* and *not ok 19* (`# pass 17 / # fail 2`).
With either cut, a stale input would start a node (#1211's sin), and an
undeclared loop would validate. Removing the modules themselves is not a
useful knife: the test file cannot import them at all, which says
nothing about which rule is load-bearing.
## Not run
- `npm run lint` (oxlint + `arch-docs`): this machine has no
`dev/harness` (no `git`, so `npm run harness` cannot run either), and
`arch-docs` needs `git log`. CI's `package` job runs both on the tip.
- The full `npm test`: the box refuses it with `pthread_create: Resource
temporarily unavailable`; the four suites above are the ones this change
can reach, and CI runs the whole suite.
- `node scripts/ds-bot-patches.mjs generate`: it needs `git` for the
diff of `ds-bot/` against `core/`, so the `files/` mirrors were written
whole and compared byte for byte; `check` passes.
- **Four review points left as they are, deliberately, this round**
(they are not this INC and changing them would widen the diff): `bot`
and `role` are still two words for the same thing in the docs; a `guard`
with an unknown `is` still defaults to `present` instead of refusing;
`producersOf` still keys on the raw spec text rather than the parsed
one; and `resolvedEdges` still reports `artifact` and `carries` for the
same value. They are listed here so step 2 does not read this half as
settled on them.
## Risk
The engine is not wired to anything yet, so a defect here cannot block a
real card; the risk is the opposite — step 2 may find a shape this half
got wrong (a fact that needs two revisions, a node that needs an input
it does not declare). `blank` names Bots that no team has, so it
validates only against its own three names; the Workflows page must let
an owner map them. And a custom workflow may be looser than GitHub's
rules: the engine's `ok` is not permission, and step 2/3 must never
report a merge or a queue entry the underlying system refused — in
GitHub's words, not the engine's. The four review points under `## Not
run` are the same kind of residue: an unknown guard `is` reads as
`present`, so a typo in a definition is a no-op guard rather than a
refusal, and `producersOf` keyed on raw text would let two spellings of
one fact both produce it. Both are worth fixing before the `repo-dev`
preset starts writing facts.
<!-- flybot:checkpoint -->
```json
{
"pr": 1291,
"claim": "Dev3",
"issue": 1290,
"summary": "A workflow node now has a typed input check: artifact.js keeps an artifact's revision, so present and current are separate questions, and an unreadable fact refuses instead of passing. workflow.js validates a definition with its own sentence per defect, draws the columns from the node ids, lays the flowchart out as a pure function, and refuses a node whose input is missing or stale with both revisions named. The blank preset is plain data, and a five-node content workflow with no repository goes through the same calls. Pure half only: no host, no UI, no GitHub.",
"files": ["core/src/shared/artifact.js", "core/src/shared/workflow.js", "core/src/shared/workflow-blank.js", "core/test/workflow.test.js", "docs/subsystems/core-host.md", "patches/ds-bot/files/src/shared/artifact.js", "patches/ds-bot/files/src/shared/workflow.js", "patches/ds-bot/files/src/shared/workflow-blank.js", "patches/ds-bot/files/test/workflow.test.js"],
"checks": { "ran": "node --import ./test/harness-resolve.mjs --test test/workflow.test.js test/board-card.test.js test/kanban.test.js test/delivery-view.test.js && node scripts/ds-bot-patches.mjs check", "result": "green", "at": "8f1864ff0c6f22843251c2c98137c873f6017bf9" },
"next": "step 2 — the repo-dev preset and the fact providers"
}
```
<!-- /flybot:checkpoint -->
1 parent 63f81f0 commit ef5dae4
9 files changed
Lines changed: 2450 additions & 0 deletions
File tree
- core
- test
- docs/subsystems
- patches/ds-bot/files
- test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
0 commit comments