feat(datetime): Raft-only source-publish lane (task #106) - #132
Draft
bytemain wants to merge 26 commits into
Draft
feat(datetime): Raft-only source-publish lane (task #106)#132bytemain wants to merge 26 commits into
bytemain wants to merge 26 commits into
Conversation
bytemain
force-pushed
the
ccwow2/datetime-raft-publish
branch
from
August 8, 2026 14:52
adaf5bd to
e9d43b7
Compare
New versions of DatetimeKMM now publish ONLY to Raft Artifacts; the GitHub Packages write side is removed end to end (workflow, script, Gradle repository). Design frozen by reviewer/platform gates: - stage-first: the Gradle build has no remote repository at all. Publications stage into a task-scoped file Maven repository (DATETIME_STAGING_DIR, asserted empty at start) -- zero publishToMavenLocal anywhere in the lane, including the CI legal gates and the manifest inventory check. - raft-publish.py generates the authority manifest ONLY from staging: exact-set equality (old versions / maven-metadata.xml / sidecars are contamination), and every staged POM must carry dev.raft.sourceSha + scm tag equal to the dispatch SHA (cross-binding). - conflict-first classification via the public control-plane listing plus per-path HEAD/GET: ALL_ABSENT -> create-only PUT (server 409 stops the run, original bytes preserved); ALL_PRESENT_IDENTICAL -> zero PUTs but still a full GET+SHA compare against the staged bytes (verified no-op, not a blind skip); partial/extra/different -> 0 PUT fail. - every classifier/readback HTTP carries the stable non-credential UA (edge 403/1010 bans library defaults before the Worker is reached); credentials stay confined to the pinned Maven origin; the control-plane listing is unauthenticated by design. - write jobs run under the raft-artifacts-production environment with contents:read only (no packages:write anywhere), publish the receipt artifact (manifest + plan + readback receipt cross-binding version/source-exact/prefix), and keep the stale-rerun guard. - the one-shot #99 mirror lane (workflow + scripts + suite) is deleted here as agreed; the read-only authority readback lane is untouched. Teeth: 40 fake-transport python teeth + 13 bash admission teeth + 84/84 readback teeth green; contamination / POM-binding / 409 guards mutation-tested red by named teeth; negative evidence asserts no GithubPackages/RaftArtifacts repository task exists. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
bytemain
force-pushed
the
ccwow2/datetime-raft-publish
branch
from
August 8, 2026 14:59
e9d43b7 to
70af28a
Compare
added 2 commits
August 8, 2026 15:43
Hosted caught it: the OHOS tree builds from datetime/build.ohos.gradle.kts (settings.ohos.gradle.kts points both project build files there), which still declared the githubPackages remote repository -- the OHOS compile job's :datetime:publish tried a GitHub PUT (401), and the task-existence gate found no staging tasks in that tree. Same migration as the normal tree: staging file repo only, lazy dispatch-SHA provenance, no remote repository and no GitHub credentials. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…t (task #107 review) Closes the four FINAL HOLD blockers on 9ee599b: B1 (aggregate state machine): the workflow is restructured into stage x4 (credential-free shard staging + shard manifest) -> ONE plan job (merge shard manifests, single conflict-first classification over the whole version) -> shard publish/verify x4 driven by the global decision. A globally partial state now fails before any PUT instead of being repaired piecemeal; a globally complete state is a verified-noop green, and the contradictory stale-rerun guard is gone. B2 (zero-PUT failure): publish is two-phase -- every selected staged file is re-hashed and the staging repo re-enumerated (added files count as failures) BEFORE the first PUT. Teeth assert a late digest failure and an added primary both issue exactly zero PUTs. B3 (dispatch + receipt binding): admission requires a 40-hex source_sha proven equal to the dispatch SHA and current landed master. New token-receipt subcommand: server-side introspection (GET /api/tokens) matches the locally computed token hash against exactly one server record and asserts unrevoked/unexpired/expected cap; only the 16-hex prefix and fullHashMatchedLocally=true leave the runner. The terminal aggregate receipt cross-binds merged manifest + global plan + shard receipts + token receipt + landed-master identity. B4 (coverage + causal negatives): DatetimeKMM tests now trigger on the production Raft workflow path; workflow-contract teeth assert zero packages:write, zero GitHub writer credential/host, environment-pinned publish jobs that all need the plan job, credential-free stage jobs, and the aggregate receipt, in both build trees via the existing task-existence negative evidence. 69/40+13 (now 69 python) + 84 readback teeth green. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
bytemain
marked this pull request as draft
August 8, 2026 16:30
added 23 commits
August 8, 2026 16:55
… binding (task #107) Closes the second FINAL HOLD's four blockers on 0f5c77f: 1. Release-wide zero-PUT barrier: the aggregate plan job now REVALIDATES every staged byte of the whole version (new revalidate subcommand: re-hash vs merged manifest + re-enumerate every shard staging root for added/unknown primaries) BEFORE classification -- so no parallel writer can ever start from drifted bytes, and a late failure anywhere means zero PUTs everywhere. 2. Token self-receipt is structural: exact-field grant check (scope == build.raft.kuiklybase with explicit publish permission, non-empty principal), killing the JSON-substring attack (evil-suffix scope + publish-in-label). The receipt is minted at plan time -- the first credentialed contact -- and its hash prefix rides the global plan; the terminal receipt re-introspects and asserts the same prefix, proving one token served the whole run. 3. Staged GAV/version binding: every manifest path's version directory must be the release version (or -ohos variant), and every staged POM must carry the lane groupId and a version equal to its own directory. A manifest can no longer claim one version while carrying another's bytes. 4. The workflow needs tooth now parses each publish job's needs block individually (a fabricated block without plan fails), so removing plan from any single writer turns it red. 79 python + 13 bash + 84 readback teeth green; the four new guard classes have causal teeth (revalidate drift/added/missing, substring grant attack, GAV drift, needs-block parsing). Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…und 3) Closes the third review round's four counterexamples by construction: 1. Same-bundle / release-wide zero-PUT: stage x4 produce intermediate material only; the plan job revalidates every byte, then freezes ONE content-addressed bundle (primaries only, digest recorded in the global plan). A SINGLE publish job consumes only that bundle: digest equality with the plan, all members read into memory and revalidated (exact path set, size, SHA), remote re-probed still-absent, the job's own token self-receipt minted, and the landed master re-fetched -- all before the first PUT. PUTs send only those in-memory bytes, so a post-freeze tamper can never change what the remote receives, and any barrier failure is exactly zero PUTs (teeth assert the empty PUT list for wrong digest, tampered member, occupied remote, 409 race). 2. Token identity follows the LIVE TokenSummary schema (no top-level principal; grants[].principal/scope/permissions structurally asserted). plan, publish and terminal each mint their introspection receipt from the actually injected secret; the terminal asserts one 16-hex prefix across all three (A->B->A goes red). 3. POM GAV is parsed as XML (namespace-tolerant) and bound per entry to its path coordinates: groupId == build.raft.kuiklybase, artifactId == path artifact, version == path version (or -ohos variant). evil.example:wrong-artifact:9.9.9 has a named red tooth. 4. Workflow teeth parse the real single-writer structure: one publish job, needs [plan], environment pins on plan/publish/receipt, no environment on stage jobs, zero GitHub writer surface, admission source-exact binding; fabricated blocks prove causality. 86 python + 13 bash + 84 readback teeth green. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
Mutation-proving the round-3 guards showed the artifactId path-binding had no covering tooth (neutralizing it stayed green). The new tooth drives a POM whose artifactId disagrees with its path to a named red. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
The exact-bound causal battery showed groupId equality was only guarded by the presence check; a present-but-wrong groupId (evil.example) stayed green. The new tooth pins it; the strengthened mutations now kill their named teeth on this exact. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…battery) Extracts the terminal aggregate receipt from the workflow heredoc into scripts/aggregate-release-receipt.py so teeth can drive it (the A->B->A token-identity drift and coverage cases go named-RED offline, alongside green). Adds token multi-match, live-schema shape, and the frozen-reread catcher (a bundle tampered mid-publish still cannot change what the remote receives). Workflow teeth gain receipt-needs-plan-and-publish and the fresh-master barrier ordering. Fixes a heredoc-mangled word boundary that silently weakened two regexes (literal backspace vs \b). 95 python + 13 bash + 84 readback teeth green. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
The member-set equality guard had no covering tooth; a smuggled extra bundle member now drives a named red with a zero-PUT assertion. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…nd-2 gaps) The unified exact-bound battery found the admission workflow tooth could stay green with a guard removed (substring presence matched other jobs' copies). Now scoped to the admission job block with structural markers; each of the three admission guards drives it red when removed. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…venance (task #107 r4) Closes the five FINAL HOLD classes on 4a9d3f9: 1. command_publish now re-lists the owned prefixes (a late unexpected primary stops the run before the first PUT) and re-fetches the landed master inside the command itself -- docstring and execution agree. 2. The token self-receipt requires the exact minimal cap: exactly one grant, exact lane scope, permissions only from {read,publish} with publish present, and an agent-shaped principal. Extra grants, admin, and human principals each have named red teeth. 3. POM sourceSha/scm tag are parsed as real XML nodes (namespace tolerant); comment-hidden strings can no longer spoof the binding. 4. The aggregate receipt validates every file entry's sha256/size, the plan schema (known decision, fileCount, 16-hex tokenHashPrefix required), and structurally revalidates both token receipts including expiry. 5. Workflow teeth pin the plan job's environment too. Battery evidence hygiene: the next raw log is generated with per-round bytecode-cache clearing and carries every round's applied literal, FAIL lines, control scope and restore exit. 105 python + 13 bash + 84 readback teeth green. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…gaps) The r4 battery showed the two new publish-barrier halves had no covering teeth. Now: a late unexpected primary under an owned prefix fails publish with zero PUTs, and a drifted origin/master (driven in a temp git repo with a real origin remote) fails the barrier before the first PUT. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
Hosted caught it: the runner always exports GITHUB_SHA, so the publish barrier's live master recheck fired inside offline teeth and killed the race tooth with a drift error. The helpers now strip GITHUB_SHA unless a tooth sets it explicitly, and the suite is green with and without an ambient value. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…ing (task #107 r5) Closes the fourth FINAL HOLD's classes on adc88c1: 1. The token introspection now runs FIRST in command_publish, so every mutable-state check (absent re-probe, owned-prefix re-list, landed master re-fetch) sits immediately before the first PUT -- the introspection-between-checks window is closed by construction. 2. Token principal is exact, not shape-only: RAFT_ARTIFACTS_EXPECT_PRINCIPAL (required dispatch input, fail-closed when unset) must equal the lane grant's principal id; a well-formed record for the wrong agent is red. 3. The aggregate receipt binds bytes per entry ({path, sha256, size} equality between publish receipt and merged manifest), cross-checks both fileCounts, validates the full plan schema (decision, count, ownedPrefixes, missing, 64-hex bundleSha256, 16-hex tokenHashPrefix), and now loads and validates the plan job's own token receipt as a fourth identity in the cross-check (A->B->A red). 110 python + 13 bash + 84 readback teeth green, both ambient GITHUB_SHA modes. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…task #107 r5+) Per the review authority's ruling: the expected publish principal is no longer a workflow_dispatch input (any caller could self-fill it). It now rides the protected raft-artifacts-production environment as the RAFT_ARTIFACTS_EXPECT_PRINCIPAL secret, written by the platform mint together with the short-lived token and deleted with its revocation. The three credentialed jobs read only that secret; missing or mismatched values fail closed, and no dispatch surface can override it. Workflow teeth pin both properties (no principal input; exactly three protected secret reads). 112 python + 13 bash + 84 readback teeth green, both ambient GITHUB_SHA modes. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…ti-spoof teeth (task #107 r6) Closes the fifth review round's findings on 7eea9f5: 1. The owned-prefix re-list is now the FINAL check before the first PUT (after introspection, absent re-probe and the master re-fetch), so a late injected key cannot be both post-check and pre-PUT. 2. The aggregate receipt verifies the frozen bundle itself (digest == plan.bundleSha256, member set == manifest primaries), enforces plan decision/missing coherence and count-field equality, and fetches origin/master before comparing (a stale ref is never a live master). 3. The principal sidecar teeth are structural per job block (a comment or duplicated line elsewhere cannot satisfy them), a dispatch input named expected_principal is a named red, and the aggregate script is now in the teeth workflow's path filters so aggregate-only changes run the Hosted python suite. 114 python + 13 bash + 84 readback teeth green, both ambient GITHUB_SHA modes. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…106) - raft-publish.py: replace the plain-PUT publish subcommand with release — server-side claim bound to (repository, taskId, version, sourceSha, ownedPrefixes, manifestDigest, objects), create-only invisible staging, one commit as the only visibility linearization point, abort with an immutable reason on any post-claim failure, and an inspect-driven short-circuit so an exact retry of a committed claim mutates nothing - atomic_release.py: AtomicReleaseClient (Bearer auth, stable UA, redirect refusal) plus manifest digest / owned-prefix derivation mirroring the deployed ledger semantics (raft-artifacts docs/atomic-releases.md) - workflow: publish job invokes release with RAFT_RELEASE_TASK_ID from the new required release_task_id dispatch input; comments match the atomic semantics - aggregate-release-receipt.py: accept committed receipts carrying a claimId; plan ownedPrefixes must equal the manifest-derived prefixes - teeth: FakeAtomicServer ledger (digest recompute, canonical/overlap/ ordinary-writer fences, identity retry vs conflict, immutable abort reason), mid-stage crash zero-public-mutation, idempotent retry, digest forgery, frozen-bytes causal tooth; 122 teeth in both GITHUB_SHA modes, 13 admission teeth, 84 authority-readback teeth — all green Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…(task #106) Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…omic client trigger (task #107) Closes BiSheng FINAL HOLD on 80fa8da: 1. Workflow: release keeps publish-receipt.json (status=committed + claim identity); verify writes verify-receipt.json. noop-verified still uses verify → publish-receipt.json as complete. Aggregate upload includes the separate verify receipt when present. 2. Aggregate: publish plan requires committed + claimId/taskId/manifestDigest/ commitReceipt/ownedPrefixes; noop-verified requires complete and rejects atomic fields. Real-shape negative teeth cover publish+complete, committed missing taskId/commitReceipt, and noop+claimId. 3. Hosted trigger: atomic_release.py listed in both path blocks of the authority-teeth workflow; trigger self-test now guards raft-publish, atomic_release, aggregate, and test-raft-publish. Local: 129/0 dual GITHUB_SHA, admission 13/0, authority-readback 88/0. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…back (task #107) Closes BiSheng FINAL HOLD on 0bb44bc: 1. release: after commit transport/error, inspect claim before abort; if already committed, recover server-backed committed receipt (no abort) and write durable publish-receipt.json. Stage-time failures still abort. 2. aggregate: recompute object-manifest digest; bind RAFT_RELEASE_TASK_ID; require commitReceipt.state=committed; require verify-receipt.json and exact {path,sha256,size} readback match. Workflow receipt job now has RAFT_RELEASE_TASK_ID. Negative teeth cover wrong-task/bad-digest/ staged-commit/missing-verify/bad-verify-sha. 3. Replace tautological retry assertions with same-claim + idempotent committed checks; add commit-response-loss recovery teeth. Local: 139/0 dual GITHUB_SHA, admission 13/0, authority-readback 88/0. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
- Gate verify-receipt validation behind require_verify_readback so a single-flag regression is a named RED (missing/bad verify teeth). - Use wrong 64-hex digest for recompute tooth; keep separate non-hex tooth. 140/0 dual GITHUB_SHA. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…efixes Closes BiSheng FINAL HOLD on 0c81c24: 1. Commit-response ambiguity: HTTP success with empty/malformed body ({}) raises into the same inspect-before-abort recovery as transport loss; committed claims emit durable publish-receipt without abort. 2. ownedPrefixes are trailing-slash canonical end-to-end (classifier plan, atomic claim/receipt, aggregate manifest derivation). E2E tooth proves plan.ownedPrefixes == release receipt.ownedPrefixes. 3. New teeth: commit_malformed_success_* (4), e2e_plan_receipt_*, owned_prefixes_*; dual GITHUB_SHA 149/0. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…on-killable - Gate full publish→committed aggregate binding behind require_publish_committed so fail-open is a named RED (not a KeyError). - Catch exceptions from commit-loss/malformed recovery teeth so RED is named rather than an unhandled crash. 149/0 dual GITHUB_SHA retained. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
BiSheng FINAL HOLD on 833f668: malformed commit success + malformed inspect success treated state=None as staged, aborted a committed claim, and left no publish-receipt. - Bounded exact-claim reconcile after commit ambiguity - Abort only on well-formed non-committed inspect - Unknown/malformed/raised inspect retries; never abort on unknown - Permanent teeth: commit_and_inspect_malformed_* and commit_inspect_unknown_* (no abort / objects stable) - 157/0 dual GITHUB_SHA Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
BiSheng FINAL HOLD on 40aaf58: public commit with no durable receipt could be reclassified as noop-verified forever, losing claimId/commitReceipt. - Persist claim-intent.json before stage/commit - recover-claim: idempotent claim+inspect → original committed receipt - noop-verified workflow tries recover-claim before generic complete - Aggregate accepts recovered committed on all-present plans - Permanent teeth: claim intent, cross-run recover, workflow recover path - 166/0 dual GITHUB_SHA Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
…arm verify Make workflow_publish_verify_does_not_overwrite scope the publish arm only (before noop-verified) and require raft-publish.py recover-claim literally so mutation battery arms kill with named RED. Signed-off-by: CC-Wow2 <raft-mobile-cc-wow2@mail.build>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
DatetimeKMM's next version publishes only to Raft Artifacts — the GitHub Packages write side is removed end to end (workflow, script, Gradle repository), per artin's standing rule and the #106 口径 (no dual-write transition; historical .0 coordinates untouched).
Design (frozen by @bisheng's 4 + @argus's 2 review gates)
DATETIME_STAGING_DIR, asserted empty at start). ZeropublishToMavenLocalanywhere — publish lane, CI legal gates, and inventory check all moved to the staging repo.raft-publish.py: manifest generated ONLY from staging (exact-set equality — old versions/maven-metadata.xml/sidecars = contamination red; every staged POM must carrydev.raft.sourceSha+ scm tag == dispatch SHA). Conflict-first classify (control-plane owned-prefix enumeration + per-path HEAD/GET):ALL_ABSENT → create-only PUT(server 409 stops the run, original bytes preserved — source-verified handler.ts);ALL_PRESENT_IDENTICAL → 0 PUT but full GET+SHA vs staged bytes(verified no-op, not blind skip); anything else → 0 PUT fail.publish-datetime-raft.yml(replaces the GitHub one): same admission/preflight-legal/stale-guard skeleton; write jobsenvironment: raft-artifacts-production+contents: readonly (nopackages: writeanywhere); per-job receipt artifact cross-binding version ↔ source exact ↔ prefix. Token absent → fail closed pre-network (proven shape: run 31252712805).Teeth
check-publish-tasks.shnow asserts noToGithubPackagesRepository/ToRaftArtifactsRepositorytask exists.Not in scope
Consumer cutover (separate gate); server-side upstream sync (botiverse/raft-artifacts#5); network/kmmresource lanes' own GitHub flows (their owners' call).
Local Gradle compile not possible on my box (kuiklybase 401) — Hosted CI is the compile gate, as established.