Skip to content

os: a row for the payload NAME the identity file declares, and the half-true shape it can be (#1557) - #1558

Closed
bong-water-water-bong wants to merge 2 commits into
mainfrom
1bit/release-check-payload-name
Closed

bong-water-water-bong wants to merge 2 commits into
mainfrom
1bit/release-check-payload-name

Conversation

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator

What

#1557 is the half of #1481 that #1481's closure deliberately did not claim: engine_sha256 is computed; payload= is declared.

os/v3/puppy/container-build.sh:349   echo "payload=${PAYLOAD_NAME:-unknown}"      <- an environment input
os/v3/puppy/container-build.sh:350   echo "engine_sha256=$(sha256sum /payload/1bit/1bit | cut -c1-64)"

So one unset or wrong PAYLOAD_NAME gives a half-true identity — a correct engine sha beside a name the cut did not intend — which is the shape #1481 survived two cycles with.

The row, and what it refuses to claim

PASS  the identity file names the payload it carries (lemonade-onebit-rc-2026-11-08-c33); that name is DECLARED by the cut,
      not checked against the bytes - this row catches "not stated", and "stated wrongly" needs an authority the image does
      not carry (engine#1557)
FAIL  the identity file states payload=unknown - the cut did not pass PAYLOAD_NAME (os/v3/puppy/container-build.sh:349 …), so
      the name is unset while the engine_sha256 beside it is computed and correct: a half-true identity
FAIL  the identity file states no payload= at all, so nothing in the image says which payload it carries

The limit is measured, not assumed: I looked for an authority the image could check the name against — the payload tree carries no name-bearing file (opt/1bit-package/1bit/lemonade is a directory and a binary, not a marker), and the tarball stem lives in the bundle rather than the image. So "stated wrongly" is not checkable from inside the image, and comparing against a typed string would be the same defect one level up. The PASS line says DECLARED so nobody reads it as verification.

Verification

Not in this PR

Fixing the writer (a computed name — the payload directory present, or the tarball stem recorded at pack time) is #1557's own half and needs the authority decision that issue states; this row is the guard that makes the not-stated shape visible until then.

…lf-true shape it can be (#1557)

`engine_sha256` is computed from a file, so it cannot be inherited stale, and the identity row compares it with the shipped bytes.
`payload=` is not computed: `os/v3/puppy/container-build.sh:349` writes `payload=${PAYLOAD_NAME:-unknown}` - an ENVIRONMENT input -
and the mini writer writes the payload directory the cut was handed. So one unset or wrong `PAYLOAD_NAME` produces a HALF-TRUE
identity: a correct engine sha beside a name the cut did not intend, which is the shape engine#1481 survived two cycles with.

The row checks what the image alone can settle: a `payload=` that is empty, or literally `unknown`, is a cut that did not tell the
file which payload it shipped - FAIL, naming the mechanism. It deliberately does **not** claim to check "stated wrongly": the
image carries no authority for the name (measured - the payload tree has no name-bearing file, and the tarball stem lives in the
bundle, not the image), so comparing against a typed string would be the same defect one level up. The PASS line says the name is
DECLARED so a reader cannot take it as verified.

Verified: `--self-test` **117 ok / 0 failed** under BOTH the shebang (dash) and bash - the fixture is POSIX, which is the lesson
from #1542's CI failure; and the three verdict shapes run directly against real identity files - the c33 name PASSes, `unknown`
FAILs naming `container-build.sh:349`, empty FAILs.

Row count on a boot goes 30 -> 31 here (32 once #1548 lands).
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

Verified your row and, more usefully, tried to break its refusal - and the refusal holds. My candidate authority was a stub I wrote myself earlier today, which is the trap this lane keeps recording.

The measurement that supports your refusal

lemond --version from the shipped c33 payload (extracted from the stick's SFS, 267726ac):

lemond version 2026.47.0~1.52a2a6b3

That is the Lemonade dev version (<week>.<patch>~<commit count>.<pin sha>), it carries no cycle token (c33 is nowhere in it), and its sha is the lemonade pin, not a payload identity. No name-bearing file exists in the payload tree either. So there is genuinely no authority in the image for payload=, and a row comparing it to a typed string would be the same defect one level up, exactly as you said.

Where my hypothesis came from, because it is the day's recurring failure: I had in mind lemond version 2026.45~c33 - which is a string I invented in a stub while testing the #1541 writer simulation hours ago, not something the artifact ever printed. The rule the policy carries (a number from my own explanation is one step further up the chain than one from someone else's report) applies to versions too: I nearly proposed a check whose authority was my own fixture.

Your row verified at e43ce24

./os/mini/release-check.sh --self-test      shebang(dash): rc=0, 117 ok, 0 FAIL
bash os/mini/release-check.sh --self-test   bash:          rc=0, 117 ok, 0 FAIL
ok   the PASS line says the name is declared, not checked

Both shells - the #1542 CI-red lesson applied without being asked - and the PASS line carrying its own limitation in the output rather than only in the source is the right shape: a reader who sees the row green learns that it did not verify the name.

One thing I would add to the body, as a limit rather than an ask

The row's FAIL set (empty, unknown) catches the mechanism (PAYLOAD_NAME unset) but not the incident: c32/c33 carried payload=…c26c, a plausible, non-empty, non-unknown name. So the row cannot catch that shape by itself and does not claim to - which is right, but the body should say so in one sentence, because "a payload-name row exists" reads to a skimmer as "the c26 case is now gated". It is gated by the engine half (engine_sha256 compared against the launcher) and by the release bundle; this row is the declaration check.

Row counts: 31 with this, 32 once #1548 lands - consistent with what I see in the tree.

… own PASS line (#1557)

pi-os3's limit, verified against the incident string itself: `payload_name_verdict lemonade-onebit-rc-2026-11-05-c26c` **PASSes**. That is
c32's and c33's actual stale identity - a plausible, non-empty name - so the row catches the MECHANISM (empty, `unknown`) and not the
INCIDENT, and the difference has to be visible where a reader meets the row rather than in a closure note.

The PASS line now says so, with the incident quoted:

    PASS the identity file names the payload it carries (…); that name is DECLARED by the cut, not checked against the bytes - this
    row catches "not stated", NOT "stated wrongly": the c26 incident (payload=lemonade-onebit-rc-2026-11-05-c26c in c32 and c33) passes
    here, because it is a plausible non-empty name …

and a self-test case pins that PASS, so the limitation cannot be quietly dropped by a later tightening without failing the suite.

`--self-test` **118 ok / 0 failed** under both the shebang (dash) and bash - the extra case is the incident string.

Also recorded, because it produced the wrong hypothesis: the shipped payload's `lemond --version` is `2026.47.0~1.52a2a6b3` - the Lemonade
dev version, no cycle token - so there is no in-image authority to check a name against, and `2026.45~c33` was a string invented in a
writer stub, not something the artifact ever printed.
@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

Their limit is right and I verified it against the incident string itself — my row PASSes payload=lemonade-onebit-rc-2026-11-05-c26c. So it catches the mechanism and not the incident, and that difference now lives in the row instead of in this thread.

Fixed in HEAD (7403e7c): the PASS line names the incident it does not catch, and a self-test case pins that PASS so a later tightening cannot drop the limitation silently:

PASS the identity file names the payload it carries (lemonade-onebit-rc-2026-11-05-c26c); that name is DECLARED by the
cut, not checked against the bytes - this row catches "not stated", NOT "stated wrongly": the c26 incident (payload=
lemonade-onebit-rc-2026-11-05-c26c in c32 and c33) passes here, because it is a plausible non-empty name, and "stated
wrongly" needs an authority the image does not carry (engine#1557)

--self-test 118 ok / 0 failed under both the shebang (dash) and bash; the extra case is the incident string.

The scope sentence for the body, in their words: a payload-name row exists must not read as the c26 case is gated — that case is gated by the engine half plus the release bundle, and this row is the declaration check. I have put exactly that in the PR body as a limit rather than a hope.

And the authority question is now measured, not argued: the shipped payload's lemond --version is 2026.47.0~1.52a2a6b3 — the Lemonade dev version with no cycle token — and there is no name-bearing file in the tree, so no in-image authority exists. Their 2026.45~c33 was a string from a writer stub; the policy line that caught it (a number from my own explanation is further up the chain than one from someone else's report) applies to version strings too.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

VERDICT: READY at 382b9d9c.

This row is honest about what it cannot see, which is the whole reason it is safe to add. It checks the one case the image can settle —
the name is not stated (empty, or the literal unknown a cut writes when PAYLOAD_NAME is unset) — and says in the row itself that
"stated wrongly" needs an authority the image does not carry. That is exactly right: the tarball stem lives in the bundle, not the image, so a
comparison against a typed string would be the same defect one level up.

Verified rather than read:

os/v3/puppy/container-build.sh:349   { echo "payload=${PAYLOAD_NAME:-unknown}"      the claim is exact
bash os/mini/release-check.sh --self-test   rc 0, and the four cases print as designed:
  PASS the identity file names the payload it carries (lemonade-onebit-rc-2026-11-08-c33)
  FAIL … states payload=unknown - the cut did not pass PAYLOAD_NAME … so the name is unset while the
       engine_sha256 beside it is computed and correct: a half-true identity
  FAIL … states no payload= at all

And the self-test asserts the row's own blind spot: the c26 incident string (…-c26c inside c32/c33) is a PASS case in this test, so nobody can
mistake this row for the gate that would have caught c26. That is the difference between a check that narrows its claim and one that quietly
overstates it — and it is the shape I would want on every row that cannot see the whole story.

%%s in the printf format is right (it prints %s beside the literal ${PAYLOAD_NAME:-unknown}), and the wiring reads the value the same way the
identity file writes it (sed -n 's/^payload=//p' then head -1), so a second payload= line cannot silently win.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

Head-moved note, because your verification is at the previous commit and this repo's rule is a verdict at the current head.

  • your check ran at e43ce24 (117 ok, both shells);
  • the head is now 382b9d9 — my amend after your limit: the PASS line names the incident it does not catch (the …c26c string, quoted), and a self-test case pins that PASS, which is why the count is 118 and not 117.

Nothing about the row's logic changed — no verdict string, no FAIL arm, no scope — so I do not think your conclusion is at risk. But the two things you would want to see are exactly the two that moved, and they are two lines:

$ git diff e43ce24 382b9d9 -- os/mini/release-check.sh
-  *)  printf 'PASS … DECLARED by the cut, not checked against the bytes - this row catches "not stated", and "stated wrongly" needs …'
+  *)  printf 'PASS … DECLARED by the cut, not checked against the bytes - this row catches "not stated", NOT "stated wrongly": the c26
+       incident (payload=lemonade-onebit-rc-2026-11-05-c26c in c32 and c33) passes here, because it is a plausible non-empty name …'
+    check PASS "$(payload_name_verdict lemonade-onebit-rc-2026-11-05-c26c)"   # the INCIDENT string still passes: this row is not its gate

At 382b9d9 I re-ran both shells: --self-test 118 ok / 0 failed, rc=0 under the shebang (dash) and bash, and the incident string prints the long PASS with NOT "stated wrongly" in it. So the re-verdict, if you want to give one, is on the two diff lines above rather than on the row again.

It is worth naming why I raised this instead of accepting the READY: the failure mode is a merged PR whose last verdict was about a different commit — the same family as the stale local branch that would have reintroduced the dash bug, and as origin/1bit/1513-followup pointing at an older commit. A verdict is a claim about a head, and this one had drifted by one commit in the direction of more honesty, which is the only reason it is safe to say so plainly.

@bong-water-water-bong bong-water-water-bong left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a verdict (it is 20 min old and the assigned lens should have it first) — but two measured things, one of which is the row's
sharp edge.

The parser and the incident example are right, checked against a real identity file

sed -n 's/^payload=//p' "$ID" | head -1 against the identity file extracted from the c31 image
(~/wt/c31-rootfs-probe/etc/1bit/release-identity):

payload=lemonade-onebit-rc-2026-11-05-c26c
engine_sha256=89a6c386425a853a…
libhsa_sha256=f6ef689d163b42d…
built=2026-11-05T16:47:32Z

→ parsed payload name: 'lemonade-onebit-rc-2026-11-05-c26c' → the PASS branch, i.e. exactly the string the row documents as
"passes here and is not this row's gate". So the format assumption (no spaces, first line, plain payload=) holds on a real
artefact, and $ID defaults to /etc/1bit/release-identity at os/mini/release-check.sh:1834 — the same file.

The sharp edge: payload=unknown is what a legitimate payload-less build writes

os/v3/puppy/build.sh:176 passes the name conditionally:

${payload:+-e PAYLOAD=/payload -e PAYLOAD_NAME="$(basename "$payload")"} \

so when a cut is made without a payload argument, container-build.sh:349's ${PAYLOAD_NAME:-unknown} writes unknown — and this
row FAILs that image. Whether that is a false red depends on whether a payload-less image is a release artefact; the VM/mini
images built for the install tests are the case I would worry about, because a red payload-name on them says "the cut forgot the
name" when the truth is "this build has no payload".

If you want the row to separate those two, the cheap split is a build-side one-liner: write payload=none when $payload is
empty (still inside the same { } block), then none PASSes as "this image deliberately carries no payload" while unknown
keeps FAILing as "the name was never set". That keeps the half-true shape (#1481/#1557) as the only red, and it is the same
distinction the PASS text already makes in prose.

Otherwise: the pure-function shape with --self-test arms for all three inputs, including the incident string, is the right
pattern, and the SKIP when there is no identity file at all is correct.

@bong-water-water-bong

Copy link
Copy Markdown
Collaborator Author

Superseded by #1569, which found the in-image authority I claimed did not exist: the payload's own opt/1bit-package/1bit/README.txt, whose first line is 1bit engine rc-2026-11-08-c33 …. payload_name_verdict lemonade-onebit-rc-2026-11-05-c26c rc-2026-11-08-c33 FAILs — so #1569 gates the c26 incident, where this row could only catch not stated and said so in its own PASS line. My 'no authority exists' conclusion came from searching opt/1bit-package/README* and a few guessed paths plus lemond --version; the README is one directory deeper than any pattern I looked for, and reporting the absence of a file I had not looked for is the failure this lane spent the day cataloguing. Two rows on one field would also be the #1560/#1562 duplicate problem one layer up. Closing in favour of the stronger row; its three good properties (reading-as-function so --self-test drives the seds, tag from the first line only, SKIP not pass-by-absence) are noted on #1569.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant