Skip to content

feat(datetime): Raft-only source-publish lane (task #106) - #132

Draft
bytemain wants to merge 26 commits into
masterfrom
ccwow2/datetime-raft-publish
Draft

feat(datetime): Raft-only source-publish lane (task #106)#132
bytemain wants to merge 26 commits into
masterfrom
ccwow2/datetime-raft-publish

Conversation

@bytemain

@bytemain bytemain commented Aug 8, 2026

Copy link
Copy Markdown
Member

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)

  • Stage-first, zero remote repository in Gradle: publications stage into a task-scoped file Maven repository (DATETIME_STAGING_DIR, asserted empty at start). Zero publishToMavenLocal anywhere — 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 carry dev.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.
  • Transport: feat(network): move iOS buffered curl to multi owner #104-reviewed pattern — HTTPS only, exact origin pin, redirect reject, HEAD/GET/PUT only, stable non-credential UA on EVERY request (edge 403/1010), credentials confined to the pinned Maven origin; control-plane listing unauthenticated by design.
  • Workflow publish-datetime-raft.yml (replaces the GitHub one): same admission/preflight-legal/stale-guard skeleton; write jobs environment: raft-artifacts-production + contents: read only (no packages: write anywhere); per-job receipt artifact cross-binding version ↔ source exact ↔ prefix. Token absent → fail closed pre-network (proven shape: run 31252712805).
  • One-shot feat(network): add request-scoped HTTP/3 runtime #99 mirror lane deleted here (workflow + scripts + suite) as agreed — closes the ci(datetime): retire the one-shot Raft mirror after verified execution #131 content; the read-only authority readback lane is untouched.

Teeth

  • 40 fake-transport python teeth + 13 bash admission teeth + 84/84 readback teeth — all green.
  • Mutation-tested red by named teeth: staging contamination, POM sourceSha cross-binding, 409 race guard, partial/foreign/digest states, UA + credential confinement.
  • Negative evidence: check-publish-tasks.sh now asserts no ToGithubPackagesRepository/ToRaftArtifactsRepository task exists.
  • PR/Hosted carries NO real writer dispatch — the real run needs landed master + task-minted token + independent review + explicit publication authority.

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.

@bytemain
bytemain force-pushed the ccwow2/datetime-raft-publish branch from adaf5bd to e9d43b7 Compare August 8, 2026 14:52
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
bytemain force-pushed the ccwow2/datetime-raft-publish branch from e9d43b7 to 70af28a Compare August 8, 2026 14:59
CC-Wow2 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
bytemain marked this pull request as draft August 8, 2026 16:30
CC-Wow2 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>
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