From 9cf933c955233ff4e1c91788a97379d7085c9c2c Mon Sep 17 00:00:00 2001 From: fadi-dev-1 Date: Tue, 10 Nov 2026 20:41:56 +0100 Subject: [PATCH 1/5] =?UTF-8?q?claim=20=E2=80=94=20another=20cycle's=20sta?= =?UTF-8?q?rt=20branch=20is=20never=20this=20deliverable's,=20and=20a=20re?= =?UTF-8?q?newal=20starts=20one?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A cycle's D5 claim answered "work continues on d5-…" with the start branch of an earlier cycle's D5, a different title, never deleted: any d-… branch counted as the deliverable's work. Once that branch was gone, claiming again only renewed the hold and made no work branch. A branch whose first commit off the default branch is a claim's start for another title of the same id is now an earlier cycle's, and the claim makes its own. A renewed hold with no work branch left gets its first one again, unless the cycle's default branch has merged the deliverable; with one, the renewal says nothing of it. project.md says both. Five tests, two seen red first; the merge guard's seen red without it. Agent-Session: 656a02b7 Co-Authored-By: Claude Opus 5.5 --- platform/tests/test_hold.py | 58 ++++++++++++++++++++++++++++ skeleton/.nstack/handbook/project.md | 6 ++- src/napkinstack/hold.py | 32 ++++++++++++--- 3 files changed, 90 insertions(+), 6 deletions(-) diff --git a/platform/tests/test_hold.py b/platform/tests/test_hold.py index 3ed5f7ff..ca244712 100644 --- a/platform/tests/test_hold.py +++ b/platform/tests/test_hold.py @@ -890,6 +890,64 @@ def test_another_deliverable_s_branch_is_not_taken_for_this_one(shared, capsys): assert git(shared, "rev-parse", "--verify", "--quiet", "d1-resume-an-interrupted-recording") +def _start_branch(shared: Path, branch: str, title: str) -> None: + """A branch `claim` started for a D1 titled otherwise — an earlier cycle's, never deleted.""" + tree = git(shared, "rev-parse", "main^{tree}") + start = git(shared, "commit-tree", tree, "-p", "main", "-m", f"Start D1 — {title}") + git(shared, "update-ref", f"refs/heads/{branch}", start) + + +def test_another_cycle_s_start_branch_of_the_same_id_is_not_taken_for_this_one(shared, capsys): + _start_branch(shared, "d1-an-older-title", "An older title") + capsys.readouterr() + assert hold.claim(clone(shared, "alice"), "D1", session="alice-d1", person="@alice") == 0 + out = capsys.readouterr().out + assert "work continues on d1-an-older-title" not in out, out + assert "work branch d1-resume-an-interrupted-recording created" in out, out + assert git(shared, "rev-parse", "--verify", "--quiet", "d1-resume-an-interrupted-recording") + + +def test_this_deliverable_s_own_start_branch_is_still_taken(shared, capsys): + _start_branch(shared, "d1-resume-an-interrupted-recording", "Resume an interrupted recording") + capsys.readouterr() + assert hold.claim(clone(shared, "alice"), "D1", session="alice-d1", person="@alice") == 0 + assert "work continues on d1-resume-an-interrupted-recording" in capsys.readouterr().out + + +def test_a_renewed_hold_with_no_work_branch_makes_one(shared, capsys): + alice = clone(shared, "alice") + hold.claim(alice, "D1", session="alice-d1", person="@alice") + git(shared, "branch", "-D", "d1-resume-an-interrupted-recording") + git(alice, "branch", "-D", "d1-resume-an-interrupted-recording") + capsys.readouterr() + assert hold.claim(alice, "D1", session="alice-d1", person="@alice") == 0 + out = capsys.readouterr().out + assert "D1 renewed" in out and "work branch d1-resume-an-interrupted-recording created" in out, out + assert git(shared, "rev-parse", "--verify", "--quiet", "d1-resume-an-interrupted-recording") + + +def test_a_renewed_hold_whose_work_is_merged_makes_no_branch_again(shared, capsys): + alice = clone(shared, "alice") + hold.claim(alice, "D1", session="alice-d1", person="@alice") + git(alice, "commit", "-q", "--allow-empty", "-m", "Resume an interrupted recording (D1) (#5)") + git(alice, "push", "-q", "origin", "main") + git(shared, "branch", "-D", "d1-resume-an-interrupted-recording") + git(alice, "branch", "-D", "d1-resume-an-interrupted-recording") + capsys.readouterr() + assert hold.claim(alice, "D1", session="alice-d1", person="@alice") == 0 + assert "work branch" not in capsys.readouterr().out + assert not git(shared, "for-each-ref", "refs/heads/d1-*") + + +def test_a_renewed_hold_with_its_work_branch_says_nothing_of_it(shared, capsys): + alice = clone(shared, "alice") + hold.claim(alice, "D1", session="alice-d1", person="@alice") + capsys.readouterr() + assert hold.claim(alice, "D1", session="alice-d1", person="@alice") == 0 + out = capsys.readouterr().out + assert "D1 renewed" in out and "work branch" not in out and "work continues" not in out, out + + # --- D165: a hold lasts its cycle (#280) ------------------------------------------------------- @pytest.mark.parametrize("after", ["between two cycles", "under the next cycle"]) diff --git a/skeleton/.nstack/handbook/project.md b/skeleton/.nstack/handbook/project.md index d76fd9f1..b7032c26 100644 --- a/skeleton/.nstack/handbook/project.md +++ b/skeleton/.nstack/handbook/project.md @@ -177,7 +177,11 @@ nstack claim D30 --take-over --person @alice # a hold nobody worked o Two agents one person runs are two holders: a session renews only a hold it is on. Another session of that person is refused like anyone else, and takes the hold with `--take-over` when -the person says so, stale or not; a person with no `--session` renews their own. +the person says so, stale or not; a person with no `--session` renews their own. A renewed +hold with no work branch left gets its first one again, unless the deliverable is merged. + +A branch its claim started for another title of the same id — an earlier cycle's `D30` — is +never this deliverable's work: the claim makes its own beside it. When a deliverable's work went on other branches, the empty branch its claim started stays: `nstack plan` names it once the default branch records the deliverable's delivery — its diff --git a/src/napkinstack/hold.py b/src/napkinstack/hold.py index ee4d200c..66c619ed 100644 --- a/src/napkinstack/hold.py +++ b/src/napkinstack/hold.py @@ -251,24 +251,40 @@ def slug(title: str) -> str: return "-".join(kept)[:40] or "work" -def _work_branch(root: Path, name: str | None, ident: str, title: str, session: str | None) -> tuple[str, str | None]: +def _started_for_another(root: Path, ref: str, base: str, ident: str, title: str) -> bool: + """The branch's first commit off the default branch is a start `claim` made for another title.""" + first = _git(root, "log", "--reverse", "--format=%s", f"{base}..{ref}").stdout.splitlines()[:1] + start = f"Start {ident} — " + return bool(first) and first[0].startswith(start) and first[0] != start + title + + +def _work_branch(root: Path, name: str | None, ident: str, title: str, session: str | None, + renew: bool = False) -> tuple[str, str | None]: """The deliverable's first work branch, from the default branch, unless it exists: what is - said, and the branch when it was made and pushed.""" + said, and the branch when it was made and pushed. A renewed hold says nothing of a branch that + exists, and starts none once the cycle's default branch has merged the deliverable.""" branch = f"{ident.lower()}-{slug(title)}" # Any d-… branch is the deliverable's work, as the hold's freshness reads it: the one checked # out first, then the remote's, then this checkout's — never a second one beside it (#232). + # A branch `claim` started for another title of the same id is an earlier cycle's, never this one's. current = _git(root, "branch", "--show-current").stdout.strip() pattern = f"{ident.lower()}-*" - shared = _git(root, "for-each-ref", "--format=%(refname:lstrip=3)", f"{MIRROR}/work/{pattern}").stdout.split() if name else [] - local = _git(root, "for-each-ref", "--format=%(refname:short)", f"refs/heads/{pattern}").stdout.split() + base = f"{MIRROR}/base" if name else "refs/heads/main" + shared = [one for one in (_git(root, "for-each-ref", "--format=%(refname:lstrip=3)", f"{MIRROR}/work/{pattern}").stdout.split() + if name else []) if not _started_for_another(root, f"{MIRROR}/work/{one}", base, ident, title)] + local = [one for one in _git(root, "for-each-ref", "--format=%(refname:short)", f"refs/heads/{pattern}").stdout.split() + if not _started_for_another(root, f"refs/heads/{one}", base, ident, title)] existing = [current] if current in local else shared + [one for one in local if one not in shared] + if existing and renew: + return "", None if existing: found = existing[0] if name and found not in shared: return (f"work continues on {found}, which exists here only: no other branch is made.\n" f" Next: git push -u {name} {found} — the hold stays fresh with the remote's work"), None return f"work continues on {found}, which exists: no other branch is made", None - base = f"{MIRROR}/base" if name else "refs/heads/main" + if renew and ident in _read_merges(root, name, plan.accepted_cycle(root)[1]).live: + return "", None if _git(root, "rev-parse", "--verify", "--quiet", base).returncode != 0: return "no work branch created: the default branch cannot be read.\n Next: create d-… yourself.", None tree = _git(root, "rev-parse", f"{base}^{{tree}}", check=True).stdout.strip() @@ -471,6 +487,12 @@ def claim(root: Path, ident: str, session: str | None = None, person: str | None made, branch = _work_branch(root, name, ident, title, session) print(made) print(_show(root, name, ident, title, person, branch, previous) + ".") + else: + made, branch = _work_branch(root, name, ident, title, session, renew=True) + if made: + print(made) + if branch: + print(_show(root, name, ident, title, person, branch, previous) + ".") return 0 From 467452c4ba0d1cc4569a8a9ac6d6ca9da51095e1 Mon Sep 17 00:00:00 2001 From: fadi-dev-1 Date: Tue, 10 Nov 2026 22:21:11 +0100 Subject: [PATCH 2/5] =?UTF-8?q?D210=20=E2=80=94=20the=20register's=20row?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Session: 656a02b7 Co-Authored-By: Claude Opus 5.5 --- docs/governance/workstreams.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/governance/workstreams.md b/docs/governance/workstreams.md index 26903274..02010663 100644 --- a/docs/governance/workstreams.md +++ b/docs/governance/workstreams.md @@ -223,6 +223,7 @@ state to its index: [`docs/adr/`](../adr/README.md), [`docs/pdr/`](../pdr/README | D207 | **An attested project's screenshots had no route** (#341). On the third project, with `checks: attested`, two criteria asked for screenshots in the sheet: a verifier found them named only, paths in a scratch folder gone after the run. The playbook asked for a CI artefact — none with attested checks — or an image attached to the pull request — which an App cannot do through the API. The project committed small PNGs under the module's `docs/evidence/`, unguided | **Done**: the verification playbook names the route — the verifier commits the screenshots and videos proving a criterion on a branch of its own, `evidence/`, never on the change's branch: its head stays the one verified (T4) and the verifier writes no commit of the change (T2) —, links each file from the sheet at that branch's commit, made-up data only, each file read before it is pushed, bounded by the `check-added-large-files` hook as it is committed; the branch goes once the cycle is closed. `tests.md`, `verification.md`'s automated step and `.gitignore` say it. Not built: an upload by `nstack attest`, weight for no user yet. A fresh verifier's finding fixed: a first draft committed beside the module, which T2 and T4 refuse. Test seen red first: the playbook names the branch and the hook, and the skeleton runs it | | D208 | **A person named verifier was the author's word** (#363). On the third project, 2026-11-04, pull request #643, opened by an agent App, said `Verifier: @napkinstack-admin` over results, evidence and commits it had written itself; that account never touched the pull request. T2 compared the name with the change's authors only: the sheet passed it, and on a project that owes no approval the merge waited on T4 alone, which the author turns green by running its own tests at the head. The project's session caught it by reading | **Done**: `nstack pr-check` reads, in one GraphQL query, whose hand is on the pull request — its description's editors, its reviewers, its commenters — and T2 refuses a person named verifier who has none — each person named, so that a second name does not carry the first; a bot named with its `[bot]` suffix is the same account. A verifier session is out of scope: it acts under the same App as the author's. Unread — no token, no answer, a field refused, more than one page —, it is said as a warning, never failed; on a workstation and under `nstack attest`, with no pull request number or recording the head, it is not read. The skeleton's `pull-request.yml` gives it `PR_NUMBER` and the job's token, with `pull-requests: read`, and runs again on a submitted review, as it does on an edit of the description; a comment is read at the next run. `RULES.md`, `05-workflow.md` §7 and `verification.md` say it. A fresh verifier's findings fixed: a field refused by the forge crashed the check, a review did not start it again, a second name carried the first, and four mutants survived. Test seen red first: the case of #643 refused, each kind of hand accepted, the query's shape and its unread answers, and the workflow's wiring | | D209 | **An unattested head read "compliant" on the forge** (#367). On a fourth project, 2026-11-07, `checks: attested`, a pull request was opened with no record on its head: the CI job "Test sheet and cycle" said `attested — judged where nstack attest runs, not in CI` and `Pull request rules: compliant.` — `check_attested` returned before reading anything under GitHub Actions, the checkout keeping no credential to fetch the notes; the status `nstack / local checks` is posted only by a session holding a token, and that one had none. Only an observer's local `pr-check` said A1 | **Done**: the record is read from the remote's notes in CI as on a workstation — the fetch carries the job's token in git's environment (`http.extraheader`), masked for the runner, never in an argument a log shows, only towards GitHub and only under GitHub Actions — on a workstation the remote's own credential answers, an expired token in the environment would refuse it; an unattested head is refused (A1), a failed or incomplete record too (A2). A remote that cannot be read — no token, a refusal — is a warning that names why, never the silent line. The A1 action says the check reads the record again at its next run, a push or an edit of the description. `07-governance.md` §7, `RULES.md` and the workflow's `contents: read` say it. Tests seen red first: a record pushed with the notes, read by a fresh checkout under CI; no record on the remote refused; a failed record refused; no remote, and a remote that refuses, warned with git's reason; notes listed but not fetched unread; the token reaching git in CI and never on a workstation. A fresh verifier's findings fixed: the token was sent on a workstation too (an expired one refused the fetch, seen on a private repository), the refusing remote was untested, git's reason could be a fragment, the secret was not masked; three mutants killed. Probed on a private repository: an installation token in the environment reads its notes | +| D210 | **`nstack claim` took an earlier cycle's start branch, then made none.** On a project, a cycle's D5 claim answered *work continues on d5-…, which exists: no other branch is made* — the start branch of an earlier cycle's D5, another title, its one empty *Start* commit never deleted on the remote: any `d-…` branch counted as the deliverable's work (D149). Once that branch was deleted, claiming again only renewed the hold, and still made no work branch | **Done**: a branch whose first commit off the default branch is a claim's start for another title of the same id is an earlier cycle's, never this deliverable's work — the claim makes its own; a hand-named branch, and this deliverable's own start, are still taken. A renewed hold with no work branch left gets its first one again, unless the cycle's default branch has merged the deliverable; with one, the renewal says nothing of it. `project.md` says both. Five tests: two seen red first, the merge guard's seen red without it | | v0.6.0 | **What is announced refuses, and the documents say what is true**: D51 to D54, and the documentary lot of the internal audit | **Done (2026-09-20)**: five workstreams merged (#83, #84, #85, #86, #87), **v0.6.0 published**, tag on `8ae544c`, verified on published artefacts — including an update of `tool-library` from v0.5.0, 9 files and no conflict, still compliant. Found doing it: D55. Next: the pilot, and M11 beside it | | Pilot | **`NapkinStack/flet`, the first real project**, run by a session that knew nothing of this repository, with the maintainer as decider and this repository's agent as observer — public on the Free plan, where the whole checklist is available, rather than private on Team where G5 is paid and G6 does not exist | **Set aside by the maintainer, 2026-10-15**, at its cycle boundary; the real product followed since is `summit`. **Ran from 2026-09-20**: discovery decided *go* after three rounds and two independent challenges, charter accepted; cycle 01 closed `stopped/reframed`, **cycle 02 closed** with two deliverables accepted and one deferred (its #27), **cycle 03 framed and approved** (#28, #30), which starts its first `critical` module; the framework taken to v0.6.1 by itself at the boundary (#29), v0.6.2 proposed (#31). It has produced D56 to D63, D68, D70 to D77, replicated D26 and D55, and measured D49 — the first figures on what an independent verifier finds (PDR-0003) | | M11 | **Ceremony follows consequence** (PDR-0011): the framework charges what a failure costs, per module and per project, and states each price before it is paid; with PDR-0004's routing, and PDR-0007, PDR-0008 and PDR-0010 — through to v0.7.0 | **Published as v0.7.0 (2026-10-12).** Planned 2026-09-23 (#98). Merged: **M11f** (#101), **M11a** (#102), **M11b** (#103), **M11d** (#104), **M11e** (#105), **M11c** (#106), **M11h** (#107), **M11g** (#108) closed by its probe — not clean, returned to M13; **M11m** (D68, D74, D75); **M11l** (PDR-0010, D59, D60); **M11n** (D79); **M11o** (PDR-0010 extended: every document has its moment); **M11p** (PDR-0002's three levels given a place: `docs/project/rules/`); **M11j** (PDR-0007: the unframed stage); **M11q** (no folder created empty; PDR-0012 proposed: a project's tree is the project's). **M11k** (PDR-0008's print mode: `nstack settings`; D77, D78). **M11s** (PDR-0012 accepted; the framework's files under `.nstack/`; an update carries what a project adapted); **M11u** (an update rewrites the old addresses a project wrote in its own files, D85). **M11t** (`nstack new-contract`: the contracts module is created by the first contract; PDR-0012 built in full); **M11v** (what the third project found: D81, D82, D83, D86, D87). **M11i**: v0.7.0 published 2026-10-12 (#125, D88). To do: **M11r** PDR-0008's apply mode and D57, after a probe on the real forge; the two earlier projects take v0.7.0 themselves, observed | From d6798262fcc718686252ba1f1191aa8140eac6d2 Mon Sep 17 00:00:00 2001 From: "napkinstack-agent[bot]" <330339777+napkinstack-agent[bot]@users.noreply.github.com> Date: Fri, 13 Nov 2026 11:37:29 +0100 Subject: [PATCH 3/5] =?UTF-8?q?claim=20=E2=80=94=20only=20an=20empty=20sta?= =?UTF-8?q?rt=20branch=20of=20another=20title=20is=20another=20cycle's?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A deliverable retitled mid-cycle kept its worked branch judged by the first commit's subject alone, so a renewal made a second d-… branch (D149). The emptiness test of the unused start branches is now one helper both read. Agent-Session: 2e1c63e2 Co-Authored-By: Claude Opus 5.5 --- platform/tests/test_hold.py | 20 ++++++++++++++++++++ skeleton/.nstack/handbook/project.md | 4 ++-- src/napkinstack/hold.py | 18 ++++++++++++------ 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/platform/tests/test_hold.py b/platform/tests/test_hold.py index ca244712..d467650e 100644 --- a/platform/tests/test_hold.py +++ b/platform/tests/test_hold.py @@ -914,6 +914,26 @@ def test_this_deliverable_s_own_start_branch_is_still_taken(shared, capsys): assert "work continues on d1-resume-an-interrupted-recording" in capsys.readouterr().out +def test_a_retitled_deliverable_s_worked_branch_is_still_its_own(shared, capsys): + alice = clone(shared, "alice") + hold.claim(alice, "D1", session="alice-d1", person="@alice") + git(alice, "switch", "-q", "d1-resume-an-interrupted-recording") + (alice / "resume.txt").write_text("work\n", encoding="utf-8") + git(alice, "add", "resume.txt") + git(alice, "commit", "-q", "-m", "Resume from the last chunk") + git(alice, "push", "-q", "origin", "d1-resume-an-interrupted-recording") + git(alice, "switch", "-q", "main") + cycle = alice / "docs/project/cycles/03-the-window.md" + cycle.write_text(cycle.read_text(encoding="utf-8").replace("Resume an interrupted recording", "Resume a cut recording"), + encoding="utf-8") + git(alice, "commit", "-q", "-am", "Retitle D1") + git(alice, "push", "-q", "origin", "main") + capsys.readouterr() + assert hold.claim(alice, "D1", session="alice-d1", person="@alice") == 0 + assert "work branch" not in capsys.readouterr().out + assert git(shared, "for-each-ref", "--format=%(refname:short)", "refs/heads/d1-*") == "d1-resume-an-interrupted-recording" + + def test_a_renewed_hold_with_no_work_branch_makes_one(shared, capsys): alice = clone(shared, "alice") hold.claim(alice, "D1", session="alice-d1", person="@alice") diff --git a/skeleton/.nstack/handbook/project.md b/skeleton/.nstack/handbook/project.md index b7032c26..a5fffee8 100644 --- a/skeleton/.nstack/handbook/project.md +++ b/skeleton/.nstack/handbook/project.md @@ -180,8 +180,8 @@ session of that person is refused like anyone else, and takes the hold with `--t the person says so, stale or not; a person with no `--session` renews their own. A renewed hold with no work branch left gets its first one again, unless the deliverable is merged. -A branch its claim started for another title of the same id — an earlier cycle's `D30` — is -never this deliverable's work: the claim makes its own beside it. +An empty branch its claim started for another title of the same id — an earlier cycle's `D30` — +is never this deliverable's work: the claim makes its own beside it. When a deliverable's work went on other branches, the empty branch its claim started stays: `nstack plan` names it once the default branch records the deliverable's delivery — its diff --git a/src/napkinstack/hold.py b/src/napkinstack/hold.py index 66c619ed..d49e98b3 100644 --- a/src/napkinstack/hold.py +++ b/src/napkinstack/hold.py @@ -251,11 +251,18 @@ def slug(title: str) -> str: return "-".join(kept)[:40] or "work" +def _empty(root: Path, ref: str, base: str) -> bool: + """One commit off the default branch, with its parent's tree: nothing was worked on it.""" + return (_git(root, "rev-list", "--count", f"{base}..{ref}").stdout.strip() == "1" + and _git(root, "rev-parse", f"{ref}^{{tree}}").stdout == _git(root, "rev-parse", f"{ref}~1^{{tree}}").stdout) + + def _started_for_another(root: Path, ref: str, base: str, ident: str, title: str) -> bool: - """The branch's first commit off the default branch is a start `claim` made for another title.""" - first = _git(root, "log", "--reverse", "--format=%s", f"{base}..{ref}").stdout.splitlines()[:1] + """An empty start `claim` made for another title: a branch with work stays the deliverable's, + whatever its title has become since.""" + subject = _git(root, "log", "-1", "--format=%s", ref).stdout.strip() start = f"Start {ident} — " - return bool(first) and first[0].startswith(start) and first[0] != start + title + return subject.startswith(start) and subject != start + title and _empty(root, ref, base) def _work_branch(root: Path, name: str | None, ident: str, title: str, session: str | None, @@ -266,7 +273,7 @@ def _work_branch(root: Path, name: str | None, ident: str, title: str, session: branch = f"{ident.lower()}-{slug(title)}" # Any d-… branch is the deliverable's work, as the hold's freshness reads it: the one checked # out first, then the remote's, then this checkout's — never a second one beside it (#232). - # A branch `claim` started for another title of the same id is an earlier cycle's, never this one's. + # An empty branch `claim` started for another title of the same id is an earlier cycle's, never this one's. current = _git(root, "branch", "--show-current").stdout.strip() pattern = f"{ident.lower()}-*" base = f"{MIRROR}/base" if name else "refs/heads/main" @@ -656,8 +663,7 @@ def _empty_starts(root: Path, name: str | None, items: list) -> list[str]: branch, ref, subject = (line.split("\t", 2) + ["", ""])[:3] ident = branch.split("-")[0] if (ident not in known or not subject.startswith(f"Start {ident.upper()} — ") - or _git(root, "rev-list", "--count", f"{base}..{ref}").stdout.strip() != "1" - or _git(root, "rev-parse", f"{ref}^{{tree}}").stdout != _git(root, "rev-parse", f"{ref}~1^{{tree}}").stdout): + or not _empty(root, ref, base)): continue if ident in states and plan.one_of(states[ident], TAKEABLE) and not _landed(root, ident, ref, base): continue From 35712b35c9b1afe293f0c75e20d6410cbdf15084 Mon Sep 17 00:00:00 2001 From: "napkinstack-agent[bot]" <330339777+napkinstack-agent[bot]@users.noreply.github.com> Date: Fri, 13 Nov 2026 11:40:21 +0100 Subject: [PATCH 4/5] =?UTF-8?q?D210=20=E2=80=94=20the=20register's=20row?= =?UTF-8?q?=20says=20an=20empty=20branch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Session: 2e1c63e2 Co-Authored-By: Claude Opus 5.5 --- docs/governance/workstreams.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/governance/workstreams.md b/docs/governance/workstreams.md index 02010663..a43138fc 100644 --- a/docs/governance/workstreams.md +++ b/docs/governance/workstreams.md @@ -223,7 +223,7 @@ state to its index: [`docs/adr/`](../adr/README.md), [`docs/pdr/`](../pdr/README | D207 | **An attested project's screenshots had no route** (#341). On the third project, with `checks: attested`, two criteria asked for screenshots in the sheet: a verifier found them named only, paths in a scratch folder gone after the run. The playbook asked for a CI artefact — none with attested checks — or an image attached to the pull request — which an App cannot do through the API. The project committed small PNGs under the module's `docs/evidence/`, unguided | **Done**: the verification playbook names the route — the verifier commits the screenshots and videos proving a criterion on a branch of its own, `evidence/`, never on the change's branch: its head stays the one verified (T4) and the verifier writes no commit of the change (T2) —, links each file from the sheet at that branch's commit, made-up data only, each file read before it is pushed, bounded by the `check-added-large-files` hook as it is committed; the branch goes once the cycle is closed. `tests.md`, `verification.md`'s automated step and `.gitignore` say it. Not built: an upload by `nstack attest`, weight for no user yet. A fresh verifier's finding fixed: a first draft committed beside the module, which T2 and T4 refuse. Test seen red first: the playbook names the branch and the hook, and the skeleton runs it | | D208 | **A person named verifier was the author's word** (#363). On the third project, 2026-11-04, pull request #643, opened by an agent App, said `Verifier: @napkinstack-admin` over results, evidence and commits it had written itself; that account never touched the pull request. T2 compared the name with the change's authors only: the sheet passed it, and on a project that owes no approval the merge waited on T4 alone, which the author turns green by running its own tests at the head. The project's session caught it by reading | **Done**: `nstack pr-check` reads, in one GraphQL query, whose hand is on the pull request — its description's editors, its reviewers, its commenters — and T2 refuses a person named verifier who has none — each person named, so that a second name does not carry the first; a bot named with its `[bot]` suffix is the same account. A verifier session is out of scope: it acts under the same App as the author's. Unread — no token, no answer, a field refused, more than one page —, it is said as a warning, never failed; on a workstation and under `nstack attest`, with no pull request number or recording the head, it is not read. The skeleton's `pull-request.yml` gives it `PR_NUMBER` and the job's token, with `pull-requests: read`, and runs again on a submitted review, as it does on an edit of the description; a comment is read at the next run. `RULES.md`, `05-workflow.md` §7 and `verification.md` say it. A fresh verifier's findings fixed: a field refused by the forge crashed the check, a review did not start it again, a second name carried the first, and four mutants survived. Test seen red first: the case of #643 refused, each kind of hand accepted, the query's shape and its unread answers, and the workflow's wiring | | D209 | **An unattested head read "compliant" on the forge** (#367). On a fourth project, 2026-11-07, `checks: attested`, a pull request was opened with no record on its head: the CI job "Test sheet and cycle" said `attested — judged where nstack attest runs, not in CI` and `Pull request rules: compliant.` — `check_attested` returned before reading anything under GitHub Actions, the checkout keeping no credential to fetch the notes; the status `nstack / local checks` is posted only by a session holding a token, and that one had none. Only an observer's local `pr-check` said A1 | **Done**: the record is read from the remote's notes in CI as on a workstation — the fetch carries the job's token in git's environment (`http.extraheader`), masked for the runner, never in an argument a log shows, only towards GitHub and only under GitHub Actions — on a workstation the remote's own credential answers, an expired token in the environment would refuse it; an unattested head is refused (A1), a failed or incomplete record too (A2). A remote that cannot be read — no token, a refusal — is a warning that names why, never the silent line. The A1 action says the check reads the record again at its next run, a push or an edit of the description. `07-governance.md` §7, `RULES.md` and the workflow's `contents: read` say it. Tests seen red first: a record pushed with the notes, read by a fresh checkout under CI; no record on the remote refused; a failed record refused; no remote, and a remote that refuses, warned with git's reason; notes listed but not fetched unread; the token reaching git in CI and never on a workstation. A fresh verifier's findings fixed: the token was sent on a workstation too (an expired one refused the fetch, seen on a private repository), the refusing remote was untested, git's reason could be a fragment, the secret was not masked; three mutants killed. Probed on a private repository: an installation token in the environment reads its notes | -| D210 | **`nstack claim` took an earlier cycle's start branch, then made none.** On a project, a cycle's D5 claim answered *work continues on d5-…, which exists: no other branch is made* — the start branch of an earlier cycle's D5, another title, its one empty *Start* commit never deleted on the remote: any `d-…` branch counted as the deliverable's work (D149). Once that branch was deleted, claiming again only renewed the hold, and still made no work branch | **Done**: a branch whose first commit off the default branch is a claim's start for another title of the same id is an earlier cycle's, never this deliverable's work — the claim makes its own; a hand-named branch, and this deliverable's own start, are still taken. A renewed hold with no work branch left gets its first one again, unless the cycle's default branch has merged the deliverable; with one, the renewal says nothing of it. `project.md` says both. Five tests: two seen red first, the merge guard's seen red without it | +| D210 | **`nstack claim` took an earlier cycle's start branch, then made none.** On a project, a cycle's D5 claim answered *work continues on d5-…, which exists: no other branch is made* — the start branch of an earlier cycle's D5, another title, its one empty *Start* commit never deleted on the remote: any `d-…` branch counted as the deliverable's work (D149). Once that branch was deleted, claiming again only renewed the hold, and still made no work branch | **Done**: an empty branch whose first commit off the default branch is a claim's start for another title of the same id is an earlier cycle's, never this deliverable's work — the claim makes its own; a hand-named branch, and this deliverable's own start, are still taken. A renewed hold with no work branch left gets its first one again, unless the cycle's default branch has merged the deliverable; with one, the renewal says nothing of it. `project.md` says both. Five tests: two seen red first, the merge guard's seen red without it | | v0.6.0 | **What is announced refuses, and the documents say what is true**: D51 to D54, and the documentary lot of the internal audit | **Done (2026-09-20)**: five workstreams merged (#83, #84, #85, #86, #87), **v0.6.0 published**, tag on `8ae544c`, verified on published artefacts — including an update of `tool-library` from v0.5.0, 9 files and no conflict, still compliant. Found doing it: D55. Next: the pilot, and M11 beside it | | Pilot | **`NapkinStack/flet`, the first real project**, run by a session that knew nothing of this repository, with the maintainer as decider and this repository's agent as observer — public on the Free plan, where the whole checklist is available, rather than private on Team where G5 is paid and G6 does not exist | **Set aside by the maintainer, 2026-10-15**, at its cycle boundary; the real product followed since is `summit`. **Ran from 2026-09-20**: discovery decided *go* after three rounds and two independent challenges, charter accepted; cycle 01 closed `stopped/reframed`, **cycle 02 closed** with two deliverables accepted and one deferred (its #27), **cycle 03 framed and approved** (#28, #30), which starts its first `critical` module; the framework taken to v0.6.1 by itself at the boundary (#29), v0.6.2 proposed (#31). It has produced D56 to D63, D68, D70 to D77, replicated D26 and D55, and measured D49 — the first figures on what an independent verifier finds (PDR-0003) | | M11 | **Ceremony follows consequence** (PDR-0011): the framework charges what a failure costs, per module and per project, and states each price before it is paid; with PDR-0004's routing, and PDR-0007, PDR-0008 and PDR-0010 — through to v0.7.0 | **Published as v0.7.0 (2026-10-12).** Planned 2026-09-23 (#98). Merged: **M11f** (#101), **M11a** (#102), **M11b** (#103), **M11d** (#104), **M11e** (#105), **M11c** (#106), **M11h** (#107), **M11g** (#108) closed by its probe — not clean, returned to M13; **M11m** (D68, D74, D75); **M11l** (PDR-0010, D59, D60); **M11n** (D79); **M11o** (PDR-0010 extended: every document has its moment); **M11p** (PDR-0002's three levels given a place: `docs/project/rules/`); **M11j** (PDR-0007: the unframed stage); **M11q** (no folder created empty; PDR-0012 proposed: a project's tree is the project's). **M11k** (PDR-0008's print mode: `nstack settings`; D77, D78). **M11s** (PDR-0012 accepted; the framework's files under `.nstack/`; an update carries what a project adapted); **M11u** (an update rewrites the old addresses a project wrote in its own files, D85). **M11t** (`nstack new-contract`: the contracts module is created by the first contract; PDR-0012 built in full); **M11v** (what the third project found: D81, D82, D83, D86, D87). **M11i**: v0.7.0 published 2026-10-12 (#125, D88). To do: **M11r** PDR-0008's apply mode and D57, after a probe on the real forge; the two earlier projects take v0.7.0 themselves, observed | From 456570c3b58763942cfbc73f058c07d2c3bc5123 Mon Sep 17 00:00:00 2001 From: "napkinstack-agent[bot]" <330339777+napkinstack-agent[bot]@users.noreply.github.com> Date: Fri, 13 Nov 2026 11:47:06 +0100 Subject: [PATCH 5/5] =?UTF-8?q?D210=20=E2=80=94=20the=20register's=20row?= =?UTF-8?q?=20counts=20six=20tests?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent-Session: 2e1c63e2 Co-Authored-By: Claude Opus 5.5 --- docs/governance/workstreams.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/governance/workstreams.md b/docs/governance/workstreams.md index a43138fc..2dc8d19b 100644 --- a/docs/governance/workstreams.md +++ b/docs/governance/workstreams.md @@ -223,7 +223,7 @@ state to its index: [`docs/adr/`](../adr/README.md), [`docs/pdr/`](../pdr/README | D207 | **An attested project's screenshots had no route** (#341). On the third project, with `checks: attested`, two criteria asked for screenshots in the sheet: a verifier found them named only, paths in a scratch folder gone after the run. The playbook asked for a CI artefact — none with attested checks — or an image attached to the pull request — which an App cannot do through the API. The project committed small PNGs under the module's `docs/evidence/`, unguided | **Done**: the verification playbook names the route — the verifier commits the screenshots and videos proving a criterion on a branch of its own, `evidence/`, never on the change's branch: its head stays the one verified (T4) and the verifier writes no commit of the change (T2) —, links each file from the sheet at that branch's commit, made-up data only, each file read before it is pushed, bounded by the `check-added-large-files` hook as it is committed; the branch goes once the cycle is closed. `tests.md`, `verification.md`'s automated step and `.gitignore` say it. Not built: an upload by `nstack attest`, weight for no user yet. A fresh verifier's finding fixed: a first draft committed beside the module, which T2 and T4 refuse. Test seen red first: the playbook names the branch and the hook, and the skeleton runs it | | D208 | **A person named verifier was the author's word** (#363). On the third project, 2026-11-04, pull request #643, opened by an agent App, said `Verifier: @napkinstack-admin` over results, evidence and commits it had written itself; that account never touched the pull request. T2 compared the name with the change's authors only: the sheet passed it, and on a project that owes no approval the merge waited on T4 alone, which the author turns green by running its own tests at the head. The project's session caught it by reading | **Done**: `nstack pr-check` reads, in one GraphQL query, whose hand is on the pull request — its description's editors, its reviewers, its commenters — and T2 refuses a person named verifier who has none — each person named, so that a second name does not carry the first; a bot named with its `[bot]` suffix is the same account. A verifier session is out of scope: it acts under the same App as the author's. Unread — no token, no answer, a field refused, more than one page —, it is said as a warning, never failed; on a workstation and under `nstack attest`, with no pull request number or recording the head, it is not read. The skeleton's `pull-request.yml` gives it `PR_NUMBER` and the job's token, with `pull-requests: read`, and runs again on a submitted review, as it does on an edit of the description; a comment is read at the next run. `RULES.md`, `05-workflow.md` §7 and `verification.md` say it. A fresh verifier's findings fixed: a field refused by the forge crashed the check, a review did not start it again, a second name carried the first, and four mutants survived. Test seen red first: the case of #643 refused, each kind of hand accepted, the query's shape and its unread answers, and the workflow's wiring | | D209 | **An unattested head read "compliant" on the forge** (#367). On a fourth project, 2026-11-07, `checks: attested`, a pull request was opened with no record on its head: the CI job "Test sheet and cycle" said `attested — judged where nstack attest runs, not in CI` and `Pull request rules: compliant.` — `check_attested` returned before reading anything under GitHub Actions, the checkout keeping no credential to fetch the notes; the status `nstack / local checks` is posted only by a session holding a token, and that one had none. Only an observer's local `pr-check` said A1 | **Done**: the record is read from the remote's notes in CI as on a workstation — the fetch carries the job's token in git's environment (`http.extraheader`), masked for the runner, never in an argument a log shows, only towards GitHub and only under GitHub Actions — on a workstation the remote's own credential answers, an expired token in the environment would refuse it; an unattested head is refused (A1), a failed or incomplete record too (A2). A remote that cannot be read — no token, a refusal — is a warning that names why, never the silent line. The A1 action says the check reads the record again at its next run, a push or an edit of the description. `07-governance.md` §7, `RULES.md` and the workflow's `contents: read` say it. Tests seen red first: a record pushed with the notes, read by a fresh checkout under CI; no record on the remote refused; a failed record refused; no remote, and a remote that refuses, warned with git's reason; notes listed but not fetched unread; the token reaching git in CI and never on a workstation. A fresh verifier's findings fixed: the token was sent on a workstation too (an expired one refused the fetch, seen on a private repository), the refusing remote was untested, git's reason could be a fragment, the secret was not masked; three mutants killed. Probed on a private repository: an installation token in the environment reads its notes | -| D210 | **`nstack claim` took an earlier cycle's start branch, then made none.** On a project, a cycle's D5 claim answered *work continues on d5-…, which exists: no other branch is made* — the start branch of an earlier cycle's D5, another title, its one empty *Start* commit never deleted on the remote: any `d-…` branch counted as the deliverable's work (D149). Once that branch was deleted, claiming again only renewed the hold, and still made no work branch | **Done**: an empty branch whose first commit off the default branch is a claim's start for another title of the same id is an earlier cycle's, never this deliverable's work — the claim makes its own; a hand-named branch, and this deliverable's own start, are still taken. A renewed hold with no work branch left gets its first one again, unless the cycle's default branch has merged the deliverable; with one, the renewal says nothing of it. `project.md` says both. Five tests: two seen red first, the merge guard's seen red without it | +| D210 | **`nstack claim` took an earlier cycle's start branch, then made none.** On a project, a cycle's D5 claim answered *work continues on d5-…, which exists: no other branch is made* — the start branch of an earlier cycle's D5, another title, its one empty *Start* commit never deleted on the remote: any `d-…` branch counted as the deliverable's work (D149). Once that branch was deleted, claiming again only renewed the hold, and still made no work branch | **Done**: an empty branch whose first commit off the default branch is a claim's start for another title of the same id is an earlier cycle's, never this deliverable's work — the claim makes its own; a hand-named branch, and this deliverable's own start, are still taken. A renewed hold with no work branch left gets its first one again, unless the cycle's default branch has merged the deliverable; with one, the renewal says nothing of it. `project.md` says both. Six tests: two seen red first, the merge guard's seen red without it | | v0.6.0 | **What is announced refuses, and the documents say what is true**: D51 to D54, and the documentary lot of the internal audit | **Done (2026-09-20)**: five workstreams merged (#83, #84, #85, #86, #87), **v0.6.0 published**, tag on `8ae544c`, verified on published artefacts — including an update of `tool-library` from v0.5.0, 9 files and no conflict, still compliant. Found doing it: D55. Next: the pilot, and M11 beside it | | Pilot | **`NapkinStack/flet`, the first real project**, run by a session that knew nothing of this repository, with the maintainer as decider and this repository's agent as observer — public on the Free plan, where the whole checklist is available, rather than private on Team where G5 is paid and G6 does not exist | **Set aside by the maintainer, 2026-10-15**, at its cycle boundary; the real product followed since is `summit`. **Ran from 2026-09-20**: discovery decided *go* after three rounds and two independent challenges, charter accepted; cycle 01 closed `stopped/reframed`, **cycle 02 closed** with two deliverables accepted and one deferred (its #27), **cycle 03 framed and approved** (#28, #30), which starts its first `critical` module; the framework taken to v0.6.1 by itself at the boundary (#29), v0.6.2 proposed (#31). It has produced D56 to D63, D68, D70 to D77, replicated D26 and D55, and measured D49 — the first figures on what an independent verifier finds (PDR-0003) | | M11 | **Ceremony follows consequence** (PDR-0011): the framework charges what a failure costs, per module and per project, and states each price before it is paid; with PDR-0004's routing, and PDR-0007, PDR-0008 and PDR-0010 — through to v0.7.0 | **Published as v0.7.0 (2026-10-12).** Planned 2026-09-23 (#98). Merged: **M11f** (#101), **M11a** (#102), **M11b** (#103), **M11d** (#104), **M11e** (#105), **M11c** (#106), **M11h** (#107), **M11g** (#108) closed by its probe — not clean, returned to M13; **M11m** (D68, D74, D75); **M11l** (PDR-0010, D59, D60); **M11n** (D79); **M11o** (PDR-0010 extended: every document has its moment); **M11p** (PDR-0002's three levels given a place: `docs/project/rules/`); **M11j** (PDR-0007: the unframed stage); **M11q** (no folder created empty; PDR-0012 proposed: a project's tree is the project's). **M11k** (PDR-0008's print mode: `nstack settings`; D77, D78). **M11s** (PDR-0012 accepted; the framework's files under `.nstack/`; an update carries what a project adapted); **M11u** (an update rewrites the old addresses a project wrote in its own files, D85). **M11t** (`nstack new-contract`: the contracts module is created by the first contract; PDR-0012 built in full); **M11v** (what the third project found: D81, D82, D83, D86, D87). **M11i**: v0.7.0 published 2026-10-12 (#125, D88). To do: **M11r** PDR-0008's apply mode and D57, after a probe on the real forge; the two earlier projects take v0.7.0 themselves, observed |