mirror of
https://github.com/przeprogramowani/10x-cli.git
synced 2026-09-19 03:30:01 +08:00
fix(cli-release-repair): bind evidence to exact sources and attempts (p1)
Bind receipts and retained stages to exact commits, run attempts and artifact IDs. Add release-only retained-byte evidence and regression coverage. Refs: https://github.com/przeprogramowani/10x-cli/pull/38, https://github.com/przeprogramowani/10x-toolkit/pull/31
This commit is contained in:
@@ -0,0 +1,40 @@
|
||||
---
|
||||
change_id: cli-release-repair
|
||||
title: Repair exact-commit CLI release evidence after merge
|
||||
status: implementing
|
||||
created: 2026-09-13
|
||||
updated: 2026-09-13
|
||||
archived_at: null
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
Repair the post-merge CLI release process and complete every authorized verification step up to the next concrete human merge or production-promotion gate.
|
||||
|
||||
Session A owns coordinated candidate variables and evidence-run pointers. CLI worktree: `/Users/admin/code/10x-cli-release-repair`, branch `fix/cli-v4-release-evidence`, base `b0c789af70f30255fb05149ad86f8534f96c2cb6`. Toolkit companion: `/private/tmp/10x-toolkit-release-repair`, same branch name, base `39925ab6155c9c17fbdabd69d160b5f4fe928c4e`. Both bases freshly fetched on 2026-09-13. Unrelated worktrees and planning artifacts remain untouched.
|
||||
|
||||
This bounded repair follows [10xdevs4-cli-access](../10xdevs4-cli-access/plan.md); its existing product decisions and Phase 4/7 obligations remain authoritative. It does not reset historical Progress or transfer ownership of migration, Circle login or EDU ZIP work.
|
||||
|
||||
Human architecture checkpoint remains explicit: choose the concrete design, review and approve the plan before `/10x-goal-implement`. The operator performs merges and separately approves production content enablement/promotion. Full operational access reaffirmed by the operator; routine reads, preparation and validation proceed without additional permission questions.
|
||||
|
||||
## Authoritative decisions — applied export
|
||||
|
||||
Operator export SHA-256: `792e4b3ba300d81e5db00f5e12dbc899eb585d6ba27639fc4411ffc871d1ddc6`. Coordinator already ran dry-run and apply through `.tmp/changes/cli-release-repair-344449df/config.json`: saved 8, conflict 0, rejected 0. Session A verified the actual export and applied owner fields; no regeneration or reapply.
|
||||
|
||||
Final choices: **D01 C, D02 A, D03 A, D04 B, D05 A with authoritative automation comment, D06 C, D07 C, D08 A**. The earlier manual-version recommendation is superseded: version is calculated automatically and prepared in the existing code PR, with no separate numbering approval. The ordinary human code merge remains. D01 C reverses bootstrap order to CLI first, Toolkit second and retains a real Toolkit companion PR prerequisite for later CLI-only work. D04 B adds a trusted central coordinator and durable lease. D06 C publishes the directory then compares registry integrity. D07 C uses manual verified partial completion.
|
||||
|
||||
## Planning revision and process
|
||||
|
||||
`10x-plan` resumed from actual applied decisions, with targeted independent feasibility research. New supplement: decision-alignment-research.md. The plan/brief are revised; the canonical targeted independent `10x-plan-review` now reports **SOUND**, all five dimensions PASS, no unresolved findings after two corrections. The report explicitly supersedes the earlier review. Implementation remains unapproved; all Progress items remain pending. Retired incompatible criteria retain unused IDs, and newly scoped criteria receive new IDs.
|
||||
|
||||
Earlier `10x-new`/`10x-research` artifacts remain history. No new framing interview is needed: the operator supplied the eight decisions and authorized this exact planning revision. No implementation clipboard command, `10x-goal-implement`, remote dispatch, publication or promotion is authorized by this export. Finish this stage with the concrete reviewed plan, its SHA-256 and any essential unresolved operator decisions.
|
||||
|
||||
Operator reported auth-access-resilience complete and archived in main EDU commit 71882433. This is coordination status only; no new dependency or EDU action is introduced.
|
||||
|
||||
## Planning handoff complete
|
||||
|
||||
Corrected plan SHA-256: `f46f2c7789a2a1844af076198ceb3c9d2dae877a7a465cdd4ffce5ef4168fb59`. Review: [reviews/plan-review.md](reviews/plan-review.md). No additional design decision is needed for this planning revision; explicit approval of this concrete plan is the implementation gate. Scoped automation activation and real release/content verification remain later prerequisites.
|
||||
|
||||
## Implementation authorization
|
||||
|
||||
2026-09-13: Operator instructed “Działaj zgodnie z 10xWorkflow/10x-goal-implement”, approving the corrected reviewed plan with SHA-256 `f46f2c7789a2a1844af076198ceb3c9d2dae877a7a465cdd4ffce5ef4168fb59`. Session A explicitly invokes 10x-goal-implement from Phase 1. Earlier unapproved statements describe planning history and are superseded by this authorization. Human merge, coordinated login and production-promotion gates remain. Structural deviations still return to the operator.
|
||||
@@ -0,0 +1,39 @@
|
||||
# Research supplement: applied operator decisions
|
||||
|
||||
Date: 2026-09-13. Scope: read-only design research and local synthetic publication probe; no remote dispatch, variable change, repository push, npm production publish or content promotion.
|
||||
|
||||
## Authoritative input
|
||||
|
||||
Export SHA-256 `792e4b3ba300d81e5db00f5e12dbc899eb585d6ba27639fc4411ffc871d1ddc6` verified locally. Applied decisions.md SHA-256 before this revision: `0e3e145d31c92f08439319df82e1ceb905d3a73c4caefa1a352109a2aa5af48f`. Selections D01 C, D02 A, D03 A, D04 B, D05 A with mandatory automation comment, D06 C, D07 C, D08 A. Coordinator reports saved 8/conflict 0/rejected 0; owner fields match the actual export. No reapply or regeneration.
|
||||
|
||||
## D01 C: source-backed bootstrap
|
||||
|
||||
CLI scripts/verify-coordinated-receipt.mjs:17 accepts canonical Toolkit push/pull_request producer identities. CLI .github/workflows/ci.yml:101 expects the exact CLI PR head; Toolkit ci.yml:96–116 checks out exact candidate variables. verify-coordinated-inputs.mjs:8 rejects dirty or wrong checkouts; it does not impose master ancestry on transient PR candidates.
|
||||
|
||||
Therefore freeze Cpr, prove Tpr/Cpr, human-merge CLI to Cm, refresh Tpr/Cm, then human-merge Toolkit to Tm. Only successful Tm push/master stage can feed the new release-only dispatch proving Tm/Cm. No PR receipt is reinterpreted as master evidence. Future CLI-only PRs still need a genuine Toolkit companion PR under the chosen C variant. The plan must expose that cost, not silently add dispatch PR mode.
|
||||
|
||||
## D05 comment: automatic number, ordinary human code merge
|
||||
|
||||
Current auto-version.mjs:26–34 derives its baseline from candidate package.version; :54–72 increments and writes it. Repeated invocation on a bot-updated PR can bump repeatedly. Current ci.yml:105,143 commits the version after evidence, causing the identity defect. src/index.ts:3,26 embeds package.json version, so stamping only registry metadata would not preserve package/binary consistency.
|
||||
|
||||
Read-only installed Bumper calculation with Angular preset and from=v1.20.0 at b0c789a returns minor for #38, hence 1.21.0. Minimum coherent repair: immutable published tag baseline, deterministic same-PR version preparation, no-op on repeat, automatic CI after the bot commit, strict rejection of stale head/base. No new numbering service and no separate version PR or approval. The comment supersedes manual-number recommendations; keeping the normal human code merge satisfies the original boundary.
|
||||
|
||||
Alternatives considered: automatic commit directly to master before a second round of evidence would preserve tested identity only after re-testing that new commit but violates the operator's no-direct-master boundary. An automatic version PR adds a second merge gate. Runtime-only version injection changes clean source/build identity and embedded version contracts. Same-PR preparation introduces the least additional mechanism that satisfies all selected constraints.
|
||||
|
||||
## D04 B: trigger, ownership and credentials
|
||||
|
||||
A trigger only on Toolkit master success misses later CLI-only master advancement. The coordinator must react to both, then independently validate current pair and successful retained source. GitHub tokens are repository-scoped; existing secret names do not prove permission scopes. Read-only inventory found CLI RELEASE_TOKEN and TOOLKIT_READ_TOKEN; Toolkit has no named CLI release-control credential. Provision exact scoped cross-repository dispatch/control credentials at activation; no values printed or requested in chat.
|
||||
|
||||
Single workflow concurrency does not establish cross-repository ownership. Proposed small append-only Git-ref lease keeps coordination in GitHub, with owner run/attempt, selected pair and child identities. Non-force fast-forward updates reject sibling commits; live service contention remains a required isolated-fixture activation check. No TTL-only stealing, no force-update and no delete/recreate of the active lock. Variables are pointers, not a locking primitive.
|
||||
|
||||
[GitHub reference API](https://docs.github.com/en/rest/git/refs#update-a-reference) defines force=false as fast-forward-only. [Workflow token behavior](https://docs.github.com/en/actions/concepts/security/github_token) explains token repository scope and trigger restrictions. [Trigger guidance](https://docs.github.com/en/actions/how-tos/write-workflows/choose-when-workflows-run/trigger-a-workflow) supports scoped App/PAT-triggered CI. [Event security guidance](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#pull_request_target) requires treating candidate contents as data in a privileged base-branch workflow. These justify the proposed contracts; they do not prove configured token scopes or a completed live lease test.
|
||||
|
||||
## D06 C: executable directory-publish probe
|
||||
|
||||
Inspected installed npm 11.12.1 lib/commands/publish.js: directory manifest preparation and libnpmpack precede libnpmpublish; directory publication repacks rather than uploading the earlier tarball. A Python loopback mock-registry probe used real npm pack and npm publish on a synthetic Git fixture with scripts disabled. Registry gitHead matched fixture SHA, tarball attachments were byte-identical, expected/actual SHA-512 matched and tracked source stayed clean.
|
||||
|
||||
Evidence: `/private/tmp/10x-release-repair-evidence-20260913/directory-publish-probe.py` and `.json`. Source fixture SHA `ab60d03b71a2d44de88fa0b9025560b63c262f5b`; integrity `sha512-PW0DmOpUbCqU9bRbriSh4jqcHeGhmSco544YHRyxA96x4j8EZzeA6NAMvdsHfsOXBZax9VSbroY7B77NDzD4Wg==`. This proves feasibility for the synthetic fixture, not the real CLI package or npm production. Directory publication retains an unavoidable detection-after-publish window; actual registry download is mandatory.
|
||||
|
||||
## Current source and external-state limits
|
||||
|
||||
Fresh fetch still yields CLI b0c789af70f30255fb05149ad86f8534f96c2cb6 and Toolkit39925ab6155c9c17fbdabd69d160b5f4fe928c4e. Both repair worktrees have only their untracked planning folder. Branch-protection GET returned 404 and branch-rules lists were empty; no controls changed. Prior production/npm/active-run observations are retained with their original timestamps and must be refreshed before any authorized operation.
|
||||
@@ -0,0 +1,412 @@
|
||||
# Naprawa wydania CLI po v4 — szczegółowe warianty i weryfikacja
|
||||
|
||||
Data: 2026-09-13. Zakres: 10x-cli i 10x-toolkit.
|
||||
|
||||
[Executive brief — tu wpisz finalne decyzje](decisions.md) · [Plan](plan.md) · [Skrót](plan-brief.md) · [Research](research.md) · [Przegląd planu](reviews/plan-review.md) · [Handoff](release-handoff.md)
|
||||
|
||||
To arkusz wyboru wariantów, nie zatwierdzenie planu ani zgoda na implementację. Alternatywy B/C nie są zaimplementowane i nie zostały dopisane do planu.
|
||||
|
||||
## Jak czytać warianty
|
||||
|
||||
- **A — obecny plan:** zachowanie opisane w `plan.md` po przeglądzie.
|
||||
- **B — najsolidniejsza alternatywa:** więcej gwarancji lub automatyzacji, zwykle za cenę nowej infrastruktury.
|
||||
- **C — najzwinniejsza alternatywa:** mniej kodu, automatyzacji lub przebiegów przy tych samych twardych ograniczeniach.
|
||||
- **Koszt budowy / utrzymania** to ocena względna, nie estymacja godzin. Praca operatora też się liczy.
|
||||
|
||||
Żaden wariant nie osłabia ścisłego SHA, pochodzenia z mastera, dowodów Linux/Windows, sanityzacji publicznych receiptów, autoryzacji kursów, ochrony danych ani harmonogramu odblokowania.
|
||||
|
||||
## Ustalenia, które są już wyraźne
|
||||
|
||||
- Operator: wersja commitowana w PR naprawczym; proponowana 1.21.0, dostępność sprawdzana przy implementacji i przed publikacją.
|
||||
- Operator: świeże dowody z ponownego testu zachowanych bajtów z kanonicznego push/master przy testowanym SHA Toolkit; bez ponownego tłumaczenia.
|
||||
- Tylko oryginalny push/master jest źródłem promocji treści; dispatch nie jest producentem wydania.
|
||||
- Merge wykonuje człowiek; login, polityka produkcyjna i promocja mają osobne zatwierdzenia.
|
||||
- Stan bazowy: 647 testów CLI PASS, npm latest 1.20.0, R2 v4 puste, `content-production` 404, backup v3 57 obiektów / 35 776 634 bajty. Naprawa niezaimplementowana.
|
||||
|
||||
## Indeks decyzji
|
||||
|
||||
| ID | Temat | Rekomendacja do wyboru |
|
||||
|---|---|---|
|
||||
| [D01](#d01) | Granica dowodu: PR CLI a wydanie z mastera | A — tryb `pr` przed mergem i świeży dowód `release` po squashu. |
|
||||
| [D02](#d02) | Tryb dowodowy: wspólny `ci.yml` czy osobny workflow | A z testem listy zadań z efektami; B, jeśli test nie jest wiarygodną blokadą. |
|
||||
| [D03](#d03) | Retencja stage, attempty i ponowienia | C — tylko attempt 1 i świeży dispatch; A przy częstych rerunach. |
|
||||
| [D04](#d04) | Skala automatyzacji sekwencji między repozytoriami | A z uproszczeniem C; B przy regularnych wydaniach. |
|
||||
| [D05](#d05) | Proces wersji po naprawie | C lub A zależnie od użycia podpowiedzi bumpu; B osobno. |
|
||||
| [D06](#d06) | Pakowanie npm: dokładny tarball i `gitHead` | A; B po potwierdzeniu provenance. |
|
||||
| [D07](#d07) | Dokończenie częściowego wydania | C z regresją zero drugiej publikacji; A po pierwszym użyciu runbooka. |
|
||||
| [D08](#d08) | Promocja v4 i zakres testów do bramek | A — osobne bramki pakietu i treści. |
|
||||
|
||||
<a id="d01"></a>
|
||||
|
||||
## D01. Tryby kandydata CLI i dowód po squashu
|
||||
|
||||
**Status:** `cli_candidate_kind` dodany po przeglądzie planu (F2); nie jest decyzją operatora.
|
||||
|
||||
**Oparcie:** Plan: implementation approach, merge order kroki 3–5; review F2.
|
||||
|
||||
### A — obecny plan
|
||||
|
||||
Dispatch Toolkit z mastera przyjmuje `cli_candidate_kind`. Tryb `pr` wymaga PR z tego samego repozytorium, którego aktualny head równa się SHA. Tryb `release` wymaga SHA równego pobranemu masterowi CLI. Po squashu wcześniejszy receipt jest nieaktualny i powstaje nowy.
|
||||
|
||||
- **Silna strona:** Dowód przed mergem i dokładny dowód wydawanego commitu; brak forków.
|
||||
- **Słaba strona:** Dwa pełne przebiegi E2E na wydanie.
|
||||
- **Tradeoff:** Czas CI za jednoznaczną tożsamość na każdym etapie.
|
||||
- **Koszt budowy / utrzymania:** średni / średni.
|
||||
|
||||
### B — najsolidniejsza alternatywa
|
||||
|
||||
Merge queue w CLI; dowód Toolkit powstaje dla commitu grupy. Zachowanie jego SHA na finalnym masterze jest warunkiem do sprawdzenia dla wybranej metody merge, nie potwierdzoną gwarancją. Jeśli SHA się zmieni, obowiązuje świeży dowód po mergu.
|
||||
|
||||
- **Silna strona:** Możliwość jednego dowodu, wyłącznie po potwierdzeniu zachowania finalnego SHA.
|
||||
- **Słaba strona:** Cross-repo dowód musi zmieścić się w cyklu kolejki; nowa konfiguracja repo.
|
||||
- **Tradeoff:** Mniej przebiegów kosztem infrastruktury i złożoności statusów.
|
||||
- **Koszt budowy / utrzymania:** wysoki / średni.
|
||||
|
||||
### C — najzwinniejsza alternatywa
|
||||
|
||||
Dowód PR przez istniejącą ścieżkę PR Toolkit z `CLI_CANDIDATE_SHA`; nowy dispatch obsługuje tylko `release`. Wariant warunkowy: obecnie nie ma potwierdzonej drogi uzyskania tego dowodu dla finalnego SHA Toolkit po jego squashu. Bez takiego dowodu wariant pozostaje zablokowany; nie wolno osłabić checka PR ani zastąpić commitu równoważnym drzewem.
|
||||
|
||||
- **Silna strona:** Mniej walidacji wejść i jeden tryb dispatch.
|
||||
- **Słaba strona:** Po mergu repair Toolkit nie ma otwartego PR do certyfikacji PR CLI; zależność od zmiennej.
|
||||
- **Tradeoff:** Mniej kodu kosztem kruchej kolejności merge.
|
||||
- **Koszt budowy / utrzymania:** niski / średni.
|
||||
|
||||
**Moja rekomendacja:** A — tryb `pr` przed mergem i świeży dowód `release` po squashu.
|
||||
|
||||
**Do sprawdzenia:** W01, W02.
|
||||
|
||||
**Finalny wybór wpisz w [karcie D01 executive briefu](decisions.md#d01).**
|
||||
|
||||
<a id="d02"></a>
|
||||
|
||||
## D02. Evidence dispatch w `ci.yml` a osobny workflow
|
||||
|
||||
**Status:** Operator wybrał ponowny test zachowanych bajtów; miejsce trybu to propozycja planu.
|
||||
|
||||
**Oparcie:** Plan: implementation approach, faza 1; research: artifact lifecycle.
|
||||
|
||||
### A — obecny plan
|
||||
|
||||
`workflow_dispatch` tylko z mastera w kanonicznym `ci.yml`. Dispatch pobiera wybrany stage, weryfikuje inventory, hash i źródło, uruchamia E2E/OpenAPI na obu systemach. Wszystkie zadania z efektami ubocznymi pozostają push-only.
|
||||
|
||||
- **Silna strona:** Jedna macierz E2E i wspólne walidatory.
|
||||
- **Słaba strona:** Bezpieczeństwo zależy od warunków `if` na każdym zadaniu.
|
||||
- **Tradeoff:** Brak duplikacji kosztem dyscypliny przy każdej zmianie CI.
|
||||
- **Koszt budowy / utrzymania:** średni / średni.
|
||||
|
||||
### B — najsolidniejsza alternatywa
|
||||
|
||||
Osobny `evidence.yml` wywołuje wspólną macierz E2E jako reusable workflow; plik nie zawiera zadań deploy, publish, R2 ani notyfikacji.
|
||||
|
||||
- **Silna strona:** Dispatch strukturalnie nie może uruchomić efektów ubocznych.
|
||||
- **Słaba strona:** Refaktor `ci.yml` i zmiana nazw jobów sprawdzanych przez weryfikator CLI.
|
||||
- **Tradeoff:** Silniejsza separacja kosztem większej zmiany w krytycznym CI.
|
||||
- **Koszt budowy / utrzymania:** wysoki / niski.
|
||||
|
||||
### C — najzwinniejsza alternatywa
|
||||
|
||||
Mały osobny workflow z kopią kroków E2E i receiptu, bez refaktoru `ci.yml`.
|
||||
|
||||
- **Silna strona:** Separacja bez naruszania głównego CI.
|
||||
- **Słaba strona:** Kopia E2E może się rozjechać z push/master.
|
||||
- **Tradeoff:** Szybka separacja kosztem duplikacji.
|
||||
- **Koszt budowy / utrzymania:** niski / średni.
|
||||
|
||||
**Moja rekomendacja:** A z testem listy zadań z efektami; B, jeśli test nie jest wiarygodną blokadą.
|
||||
|
||||
**Do sprawdzenia:** W03.
|
||||
|
||||
**Finalny wybór wpisz w [karcie D02 executive briefu](decisions.md#d02).**
|
||||
|
||||
<a id="d03"></a>
|
||||
|
||||
## D03. Retencja stage, nazwy z attemptem i reruny
|
||||
|
||||
**Status:** Ponowny test zachowanych bajtów wybrany przez operatora; polityka rerunów i retencji to propozycja planu.
|
||||
|
||||
**Oparcie:** Plan: critical implementation details, faza 1; research: attempt mixing, retained content.
|
||||
|
||||
### A — obecny plan
|
||||
|
||||
Każdy udany master zachowuje stage niezależnie od filtra zmian treści. Artefakty i receipty zawierają run ID i attempt. Normalnym ponowieniem jest świeży dispatch; rerun jest akceptowany tylko z kompletem jobów, receiptów i artefaktów tego samego attemptu.
|
||||
|
||||
- **Silna strona:** Obsługuje reruny bez mieszania generacji.
|
||||
- **Słaba strona:** Walidacja kompletu attemptu i stabilności runu to dodatkowa logika.
|
||||
- **Tradeoff:** Wygoda rerunów za więcej testów.
|
||||
- **Koszt budowy / utrzymania:** średni / średni.
|
||||
|
||||
### B — najsolidniejsza alternatywa
|
||||
|
||||
A oraz prywatne archiwum stage poza retencją GitHub, z hashem inventory i ostrzeżeniem przed wygaśnięciem artefaktu.
|
||||
|
||||
- **Silna strona:** Dowód i promocja nie zależą od terminu wygaśnięcia.
|
||||
- **Słaba strona:** Nowy magazyn, uprawnienia i ścieżka weryfikacji archiwum.
|
||||
- **Tradeoff:** Trwałość kosztem kolejnego systemu z danymi prywatnymi.
|
||||
- **Koszt budowy / utrzymania:** wysoki / średni.
|
||||
|
||||
### C — najzwinniejsza alternatywa
|
||||
|
||||
Retencja każdego udanego mastera i nazwy z attemptem jak w A, ale świeże dowody dispatch akceptowane są wyłącznie z attempt 1; każde ponowienie dowodów to nowy dispatch. Oryginalny push/master zachowuje oddzielną walidację dokładnego attemptu i kompletu jego jobów. Dispatch nie może zastąpić nieudanego producenta stage.
|
||||
|
||||
- **Silna strona:** Mniej stanów dowodowego dispatch; nadal wymagane sprawdzenie obu OS oraz źródłowego push/master.
|
||||
- **Słaba strona:** Flaky job wymaga pełnego nowego przebiegu.
|
||||
- **Tradeoff:** Czas CI zamiast kodu obsługi rerunów.
|
||||
- **Koszt budowy / utrzymania:** niski / niski.
|
||||
|
||||
**Moja rekomendacja:** C — tylko attempt 1 i świeży dispatch; A przy częstych rerunach.
|
||||
|
||||
**Do sprawdzenia:** W04, W05.
|
||||
|
||||
**Finalny wybór wpisz w [karcie D03 executive briefu](decisions.md#d03).**
|
||||
|
||||
<a id="d04"></a>
|
||||
|
||||
## D04. Orkiestracja dowodu i wydania między repozytoriami
|
||||
|
||||
**Status:** Plan zakłada ręczne dispatch Session A z inspekcją przed każdą operacją; skala automatyzacji otwarta.
|
||||
|
||||
**Oparcie:** Plan: critical implementation details, merge order, faza 4.
|
||||
|
||||
### A — obecny plan
|
||||
|
||||
Session A pobiera SHA, sprawdza aktywne i oczekujące runy oraz zmienne, uruchamia dispatch z jawnymi wejściami, rejestruje run i uruchamia wydanie CLI. Grupy concurrency nie anulują przebiegów; nieznane nowsze wartości zatrzymują operację.
|
||||
|
||||
- **Silna strona:** Brak tokenów cross-repo; każda operacja ma jawne wejścia.
|
||||
- **Słaba strona:** Ręczne przepisywanie identyfikatorów i zależność od dyscypliny.
|
||||
- **Tradeoff:** Kontrola człowieka kosztem czasu operatora.
|
||||
- **Koszt budowy / utrzymania:** niski / średni.
|
||||
|
||||
### B — najsolidniejsza alternatywa
|
||||
|
||||
Automatyczny łańcuch przez `workflow_run` / `repository_dispatch` z lease między repozytoriami.
|
||||
|
||||
- **Silna strona:** Powtarzalna sekwencja bez ręcznych błędów.
|
||||
- **Słaba strona:** Token cross-repo o szerokich uprawnieniach i trudniejsze zatrzymanie.
|
||||
- **Tradeoff:** Mniej pracy przy częstych wydaniach, większa powierzchnia ataku.
|
||||
- **Koszt budowy / utrzymania:** wysoki / średni.
|
||||
|
||||
### C — najzwinniejsza alternatywa
|
||||
|
||||
Jak A, ale ścieżka wydania bierze wyłącznie jawne wejścia dispatch; zmienne repozytorium zostają tylko wskaźnikiem dla checków PR.
|
||||
|
||||
- **Silna strona:** Mniej mutowalnego stanu do inspekcji przy wydaniu.
|
||||
- **Słaba strona:** Checki PR nadal zależą od zmiennych.
|
||||
- **Tradeoff:** Prostsza inspekcja kosztem dwóch modeli wejść.
|
||||
- **Koszt budowy / utrzymania:** niski / niski.
|
||||
|
||||
**Moja rekomendacja:** A z uproszczeniem C; B przy regularnych wydaniach.
|
||||
|
||||
**Do sprawdzenia:** W02, W06.
|
||||
|
||||
**Finalny wybór wpisz w [karcie D04 executive briefu](decisions.md#d04).**
|
||||
|
||||
<a id="d05"></a>
|
||||
|
||||
## D05. `auto-version.mjs` i przygotowanie wersji
|
||||
|
||||
**Status:** Operator wybrał wersję w PR naprawczym (1.21.0 proponowana); dalszy proces wersji to propozycja planu.
|
||||
|
||||
**Oparcie:** Plan: accepted decisions, faza 2; research: untested version commit.
|
||||
|
||||
### A — obecny plan
|
||||
|
||||
`package.json` z wersją w PR; CI waliduje zgodność wersji, tagu i npm. `auto-version.mjs` może zostać jako podpowiedź bez zapisu.
|
||||
|
||||
- **Silna strona:** Wersja przechodzi review; podpowiedź bumpu dostępna.
|
||||
- **Słaba strona:** Ręczny krok w każdym wydaniu.
|
||||
- **Tradeoff:** Kontrola człowieka za drobny koszt pracy.
|
||||
- **Koszt budowy / utrzymania:** niski / niski.
|
||||
|
||||
### B — najsolidniejsza alternatywa
|
||||
|
||||
Usługa automatycznego PR wersji z changelogiem, zatwierdzana przez człowieka.
|
||||
|
||||
- **Silna strona:** Mniej ręcznej pracy przy regularnych wydaniach.
|
||||
- **Słaba strona:** Poza zakresem naprawy; dodatkowy writer i konfiguracja.
|
||||
- **Tradeoff:** Wygoda za kolejną zależność.
|
||||
- **Koszt budowy / utrzymania:** średni / średni.
|
||||
|
||||
### C — najzwinniejsza alternatywa
|
||||
|
||||
Usunąć zapis i podpowiedź z `auto-version.mjs`; zostaje walidacja zgodności wersji z tagiem i rejestrem.
|
||||
|
||||
- **Silna strona:** Najmniej kodu; jedno źródło prawdy.
|
||||
- **Słaba strona:** Brak automatycznej podpowiedzi bumpu.
|
||||
- **Tradeoff:** Prostota kosztem wygody.
|
||||
- **Koszt budowy / utrzymania:** niski / niski.
|
||||
|
||||
**Moja rekomendacja:** C lub A zależnie od użycia podpowiedzi bumpu; B osobno.
|
||||
|
||||
**Do sprawdzenia:** W07.
|
||||
|
||||
**Finalny wybór wpisz w [karcie D05 executive briefu](decisions.md#d05).**
|
||||
|
||||
<a id="d06"></a>
|
||||
|
||||
## D06. Tarball, `gitHead` i przypięty npm
|
||||
|
||||
**Status:** Katalog pakowania z `gitHead` i npm 11.12.1 dodane po przeglądzie planu (F1); nie jest decyzją operatora.
|
||||
|
||||
**Oparcie:** Plan: implementation approach, faza 2; review F1.
|
||||
|
||||
### A — obecny plan
|
||||
|
||||
W oddzielnym katalogu dodać do manifestu pakietu tylko `gitHead` walidowanego SHA; źródło i wersja w Git bez zmian. Przypięty npm 11.12.1 (wersja weryfikowana w testach) pakuje raz; tarball przechodzi smoke, jest zachowany i publikowany. Po publikacji weryfikacja integrity, wersji i `gitHead` w rejestrze.
|
||||
|
||||
- **Silna strona:** Opublikowane bajty to bajty po smoke.
|
||||
- **Słaba strona:** Własny helper i test mock-registry.
|
||||
- **Tradeoff:** Dodatkowy kod za gwarancję przed publikacją.
|
||||
- **Koszt budowy / utrzymania:** średni / niski.
|
||||
|
||||
### B — najsolidniejsza alternatywa
|
||||
|
||||
A oraz npm provenance z OIDC i kontrola powtórnego pakowania.
|
||||
|
||||
- **Silna strona:** Publiczny, weryfikowalny związek pakietu z workflow.
|
||||
- **Słaba strona:** Zgodność provenance z publikacją tarballa niepotwierdzona; konfiguracja uprawnień.
|
||||
- **Tradeoff:** Audytowalność kosztem nowej zależności w publikacji.
|
||||
- **Koszt budowy / utrzymania:** średni / średni.
|
||||
|
||||
### C — najzwinniejsza alternatywa
|
||||
|
||||
Publikacja katalogu z checkoutu dokładnego SHA; lokalny `npm pack` w tym samym jobie i porównanie integrity z rejestrem po publikacji.
|
||||
|
||||
- **Silna strona:** npm ustawia `gitHead` sam; mało kodu.
|
||||
- **Słaba strona:** Rozbieżność wykrywamy po publikacji, a wersji nie da się nadpisać.
|
||||
- **Tradeoff:** Mniej kodu kosztem wykrywania zamiast zapobiegania.
|
||||
- **Koszt budowy / utrzymania:** niski / niski.
|
||||
|
||||
**Moja rekomendacja:** A; B po potwierdzeniu provenance.
|
||||
|
||||
**Do sprawdzenia:** W08, W09.
|
||||
|
||||
**Finalny wybór wpisz w [karcie D06 executive briefu](decisions.md#d06).**
|
||||
|
||||
<a id="d07"></a>
|
||||
|
||||
## D07. Recovery po npm success i GitHub failure
|
||||
|
||||
**Status:** Automatyczny recovery dodany po przeglądzie planu (F3); skala nie jest wybrana przez operatora.
|
||||
|
||||
**Oparcie:** Plan: critical implementation details, faza 2; review F3.
|
||||
|
||||
### A — obecny plan
|
||||
|
||||
Dispatch recovery przyjmuje oryginalny run/attempt i ID artefaktów pakietu oraz binariów. Manifest wydania wiąże SHA, wersję, dowód, integrity i hashe binariów. Recovery porównuje tarball w rejestrze, nigdy nie wywołuje `npm publish` dla istniejącej wersji i uzupełnia tylko brakujące assety.
|
||||
|
||||
- **Silna strona:** Powtarzalne dokończenie bez przebudowy i drugiej publikacji.
|
||||
- **Słaba strona:** Workflow i testy dla rzadkiej awarii.
|
||||
- **Tradeoff:** Kod na zapas za mniejsze ryzyko ręcznego błędu.
|
||||
- **Koszt budowy / utrzymania:** średni / średni.
|
||||
|
||||
### B — najsolidniejsza alternatywa
|
||||
|
||||
Najpierw szkic GitHub Release z kompletem binariów, potem npm, na końcu upublicznienie szkicu; recovery jak A.
|
||||
|
||||
- **Silna strona:** Mniej stanów częściowych.
|
||||
- **Słaba strona:** Przebudowa kolejności i sprzątanie porzuconych szkiców.
|
||||
- **Tradeoff:** Mniejsza szansa awarii za większą zmianę workflow.
|
||||
- **Koszt budowy / utrzymania:** wysoki / średni.
|
||||
|
||||
### C — najzwinniejsza alternatywa
|
||||
|
||||
Workflow odmawia publikacji istniejącej wersji i zachowuje manifest wydania; runbook opisuje ręczne dokończenie z weryfikacją integrity i hashy.
|
||||
|
||||
- **Silna strona:** Brak workflow recovery; blokada drugiej publikacji zostaje.
|
||||
- **Słaba strona:** Ręczny upload i ryzyko pomyłki operatora.
|
||||
- **Tradeoff:** Mniej kodu za pracę przy rzadkiej awarii.
|
||||
- **Koszt budowy / utrzymania:** niski / niski.
|
||||
|
||||
**Moja rekomendacja:** C z regresją zero drugiej publikacji; A po pierwszym użyciu runbooka.
|
||||
|
||||
**Do sprawdzenia:** W10.
|
||||
|
||||
**Finalny wybór wpisz w [karcie D07 executive briefu](decisions.md#d07).**
|
||||
|
||||
<a id="d08"></a>
|
||||
|
||||
## D08. Bramki pakietu i treści v4
|
||||
|
||||
**Status:** Plan rozdziela wydanie CLI od promocji; podział testów między bramki jest otwarty.
|
||||
|
||||
**Oparcie:** Plan: faza 4, merge order krok 6; handoff: content promotion, downloaded-package acceptance.
|
||||
|
||||
### A — obecny plan
|
||||
|
||||
Po publikacji: niezależne pobranie z rejestru, instalacja z wyłączonymi skryptami, help/version, potem zatwierdzony login i akceptacja v3, wyboru kursu, braku zapisów przy podglądzie, EN/PL i sync. Promocja v4 po osobnym zatwierdzeniu polityki, środowiska i dokładnych wejść; potem test tożsamości v4 i odblokowania po 2026-09-14T06:00:00Z.
|
||||
|
||||
- **Silna strona:** Wczesny dowód pakietu; brak deklaracji gotowości v4 przed dowodami.
|
||||
- **Słaba strona:** Dwie serie testów z realnym kontem.
|
||||
- **Tradeoff:** Więcej pracy za rozdzielenie ryzyk.
|
||||
- **Koszt budowy / utrzymania:** średni / średni.
|
||||
|
||||
### B — najsolidniejsza alternatywa
|
||||
|
||||
A oraz próba promocji i withdraw w osobnym buckecie przed produkcyjną promocją.
|
||||
|
||||
- **Silna strona:** Mechanika sprawdzona przed produkcją.
|
||||
- **Słaba strona:** Dodatkowe środowisko i dane.
|
||||
- **Tradeoff:** Pewność za koszt infrastruktury.
|
||||
- **Koszt budowy / utrzymania:** wysoki / średni.
|
||||
|
||||
### C — najzwinniejsza alternatywa
|
||||
|
||||
Po publikacji wykonujemy weryfikację integrity i instalację anonimową; login, v3 i v4 w jednej akceptacji po promocji. To odroczenie testów, nie ich usunięcie: weryfikacja wydania CLI i etap 1 Session C pozostają otwarte do wymaganej akceptacji rzeczywistego pakietu.
|
||||
|
||||
- **Silna strona:** Jeden login, jedna seria testów.
|
||||
- **Słaba strona:** Regresja v3 w pakiecie wyjdzie dopiero przy promocji.
|
||||
- **Tradeoff:** Mniej pracy za późniejsze wykrycie.
|
||||
- **Koszt budowy / utrzymania:** niski / niski.
|
||||
|
||||
**Moja rekomendacja:** A — osobne bramki pakietu i treści.
|
||||
|
||||
**Do sprawdzenia:** W11, W12, W13, W14.
|
||||
|
||||
**Finalny wybór wpisz w [karcie D08 executive briefu](decisions.md#d08).**
|
||||
|
||||
<a id="verification"></a>
|
||||
|
||||
## Rzeczy do sprawdzenia — wymagany dowód
|
||||
|
||||
Rejestr dowodów do zebrania; brak wpisu nie oznacza zaliczenia. „Session A” oznacza implementującego koordynatora wydania.
|
||||
|
||||
| ID | Co sprawdzić i jaki wynik zapisać | Właściciel | Kiedy wynik jest potrzebny |
|
||||
|---|---|---|---|
|
||||
| W01 | Syntetyczny cykl: merge Toolkit → dowód `pr` dla head PR CLI → squash → dowód `release` dla SHA mastera. Odmowa dla forka, nieaktualnego head, starego SHA i SHA spoza mastera. D01-B wymaga dodatkowo próby finalnego SHA merge queue; D01-C dowodu wykonalności checka PR z finalnym SHA Toolkit. | Session A | Przed review PR; wykonalność alternatyw przed ich przyjęciem |
|
||||
| W02 | Zapis inspekcji przed każdą operacją: aktualne SHA masterów, aktywne/oczekujące runy obu repo, wartości i czasy zmiennych. Nieznana nowsza wartość zatrzymuje krok. | Session A | Bezpośrednio przed każdym dispatch i publikacją |
|
||||
| W03 | Test workflow listujący zadania z efektami (transform, deploy, publish, R2, notyfikacje) i dowodzący, że dispatch żadnego nie uruchamia. | Session A | Przed PR Toolkit |
|
||||
| W04 | Master tylko API/docs zachowuje stage; poprzednie stage nie są usuwane; zapisać daty wygaśnięcia stage `10313382854` i przyszłych stage. | Session A | Przed mergem Toolkit |
|
||||
| W05 | Reprodukcja przepisania attemptu jest odrzucana; selektywny rerun bez Windows, mieszane joby, wygasły lub zduplikowany artefakt kończą się odmową bez prywatnych danych. | Session A | Przed PR Toolkit |
|
||||
| W06 | Wymagane uprawnienia tokenu cross-repo i sposób zatrzymania łańcucha; częstość wydań CLI uzasadniająca automatyzację. | Operator + Session A | Przed wyborem D04-B |
|
||||
| W07 | Brak wersji 1.21.0 i tagu `v1.21.0` w npm i GitHub. | Session A | Przy implementacji i ponownie przed publikacją |
|
||||
| W08 | Pack/extract/mock-registry: `gitHead` w tarballu, zainstalowany npm 11.12.1, integrity opublikowane równe spakowanemu, źródło i wersja w Git bez zmian. | Session A | Przed PR CLI |
|
||||
| W09 | Zgodność npm provenance z publikacją tarballa w GitHub Actions. | Session A | Przed wyborem D06-B |
|
||||
| W10 | Symulacja npm success / GitHub failure: zero drugich wywołań `npm publish`; brak lub wygaśnięcie oryginalnego artefaktu zatrzymuje dokończenie. | Session A | Przed PR CLI |
|
||||
| W11 | Pobrany z rejestru pakiet: SHA-512, `gitHead`, wersja, SHA tagu i para źródeł zgodne z dowodem; instalacja w izolacji z wyłączonymi skryptami, help/version. | Session A | Po publikacji |
|
||||
| W12 | Po zatwierdzonym loginie: status konta, najwyższy dostępny autoryzowany kurs, stabilny binding v3, brak zapisów przy podglądzie i nieudanym preflight, EN/PL, sync i ochrona lokalnych edycji. | Session A + operator | Po publikacji, przed etapem 1 Session C |
|
||||
| W13 | Prerekwizyty promocji: `content-production` z reviewerem, PR polityki z secured floor, świeży backup v3 porównany z 57 obiektami / 35 776 634 bajtami, wejścia withdraw. | Operator + Session A | Przed zatwierdzeniem promocji |
|
||||
| W14 | Produkcja po 2026-09-14T06:00:00Z: odblokowanie modułu 1 bez override KV oraz zgodność release ID i hash manifestu v4 w pobranym CLI. | Session A | Po promocji i po granicy czasu |
|
||||
|
||||
### Miejsce na wyniki weryfikacji
|
||||
|
||||
Skopiuj wiersz dla kolejnych wyników; brak wpisu nie oznacza zaliczenia.
|
||||
|
||||
| ID W… | Wynik i odnośnik do dowodu | Data / osoba | Wpływ na decyzję D… |
|
||||
|---|---|---|---|
|
||||
| … | … | … | … |
|
||||
|
||||
## Zależności między wyborami
|
||||
|
||||
- **D01 + D04:** tryb `pr` i ścieżka wydania określają, które wejścia pochodzą ze zmiennych, a które z jawnego dispatch.
|
||||
- **D02 + D03:** miejsce trybu dowodowego decyduje, które joby produkują artefakty z attemptem i które stage są zachowywane.
|
||||
- **D05 + D06:** wersja z PR i `gitHead` w tarballu razem wiążą pakiet z testowanym SHA.
|
||||
- **D06 + D07:** recovery opiera się na zachowanym tarballu i manifeście wydania; D06-C zmienia to, co można porównać przy dokończeniu.
|
||||
- **D08:** wydanie pakietu nie spełnia etapu 1 Session C bez dowodu dostarczenia.
|
||||
|
||||
## Weryfikacja zewnętrznych założeń na dzień 2026-09-13
|
||||
|
||||
Założenia o npm (publikacja tarballa bez automatycznego `gitHead`) pochodzą z inspekcji npm 11.12.1 w przeglądzie planu; o artefaktach i rerunach GitHub z dokumentacji cytowanej w research. Przy kontroli kart sprawdzono [dokumentację merge queue GitHub](https://docs.github.com/en/enterprise-cloud%40latest/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue): opisuje tymczasowe branche i różne metody merge. Nie wykonano próby potwierdzającej identyczność finalnego SHA w tych repozytoriach; D01-B pozostaje warunkowy. Bez zmian w CI lub produkcji.
|
||||
|
||||
## Uzgodnienie dokumentów po wyborze
|
||||
|
||||
Finalne wybory zapisujemy w [executive briefie](decisions.md), potem uzgadniamy `plan.md` i `plan-brief.md` oraz uzyskujemy autoryzację wynikowego zakresu. Stan realizacji pozostaje wyłącznie w sekcji Progress planu.
|
||||
@@ -0,0 +1,112 @@
|
||||
---
|
||||
record: 10x-decision-record/v1
|
||||
created: 2026-09-13T09:44:25.307Z
|
||||
author: Marcin Czarkowski
|
||||
sources:
|
||||
decisions: e597adeecc8e8a831b0d6c544a58795d15a3eaf68cb1e2d8bdd0e39bb8ff7135
|
||||
details: 1fabbcb575368f89a738bf6ea943cdbf08216d458d3c902dee0b01c567053150
|
||||
---
|
||||
|
||||
## D01 · Jak dowodzimy CLI przed mergem i po squashu
|
||||
|
||||
- sectionHash: 81c9e8c7339286fdfdb8e4d27b9c4fef63c46754c8597b800278bdf8d0c6763a
|
||||
- basisHash: c154c1cf97e5
|
||||
- action: accept
|
||||
- variant: C
|
||||
|
||||
**Twoja finalna decyzja:** C (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** …
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
|
||||
## D04 · Ile sekwencji wydania automatyzujemy
|
||||
|
||||
- sectionHash: e638c17fd05632f344dc2b56e17c701b009f8b9ba99daddfca4c98c453831450
|
||||
- basisHash: 7142d35e7877
|
||||
- action: accept
|
||||
- variant: B
|
||||
|
||||
**Twoja finalna decyzja:** B (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** …
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
|
||||
## D06 · Jak pakujemy i publikujemy npm
|
||||
|
||||
- sectionHash: 7518a765c69df8ba921f6878db64c609042ab92e63a0412f5d02b2a7ae955631
|
||||
- basisHash: c29071bc49ba
|
||||
- action: accept
|
||||
- variant: C
|
||||
|
||||
**Twoja finalna decyzja:** C (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** …
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
|
||||
## D07 · Jak kończymy częściowo udane wydanie
|
||||
|
||||
- sectionHash: ef45997aa844ec1498df2a2c9e0226015f9da7e2812942594b46ea6aea38bf5a
|
||||
- basisHash: 8c7b00733ce1
|
||||
- action: accept
|
||||
- variant: C
|
||||
|
||||
**Twoja finalna decyzja:** C (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** …
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
|
||||
## D08 · Promocja v4 i zakres testów przed jej bramką
|
||||
|
||||
- sectionHash: bf0b6346f5070a173bb5b0957066ac56d8f79a085fac1c2a6df4d679026841ca
|
||||
- basisHash: 4fc752bec70b
|
||||
- action: accept
|
||||
- variant: A
|
||||
|
||||
**Twoja finalna decyzja:** A (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** …
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
|
||||
## D02 · Gdzie żyje tryb dowodowy w Toolkit
|
||||
|
||||
- sectionHash: bdae81eca1144bfb375dc9c76180ac4f74e758f0be6e388805b6f443ff8f53db
|
||||
- basisHash: 62aa7f94824a
|
||||
- action: accept
|
||||
- variant: A
|
||||
|
||||
**Twoja finalna decyzja:** A (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** …
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
|
||||
## D03 · Retencja stage, attempty i ponowienia
|
||||
|
||||
- sectionHash: f86d440977df89dab0acbc64156955d2df81da41a6b5141933979d05fc8acfe2
|
||||
- basisHash: e5d8c40005ca
|
||||
- action: accept
|
||||
- variant: A
|
||||
|
||||
**Twoja finalna decyzja:** A (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** …
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
|
||||
## D05 · Jak dalej obsługujemy numer wersji
|
||||
|
||||
- sectionHash: 216fd48ffaf2a8d6faa675d89fcb6a95b29ba0e3192b1f7c3b472c495d3b1f53
|
||||
- basisHash: 1ca96b22e95b
|
||||
- action: accept
|
||||
- variant: A
|
||||
|
||||
**Twoja finalna decyzja:** A (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** chcę za wszelką cenę uniknąc manualnego akceptowania wersji, jeżeli możemy to utrzymać w podobnym stanie jak działało lub inaczej zapewnić automatyzację bez wzrostu złożoności - zróbmy to
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
@@ -0,0 +1,273 @@
|
||||
# Naprawa wydania CLI po v4 — executive brief do decyzji
|
||||
|
||||
Data: 2026-09-13 · Zakres: 10x-cli i 10x-toolkit · Etap: plan po przeglądzie (SOUND), przed zatwierdzeniem implementacji
|
||||
|
||||
[Plan](plan.md) · [Szczegółowe warianty i rejestr weryfikacji](decisions-details.md) · [Handoff wydania](release-handoff.md)
|
||||
|
||||
## Cel i sytuacja
|
||||
|
||||
Chcemy opublikować CLI z v4, w którym jedna zatwierdzona wersja, przetestowany commit mastera, tag, tarball npm i binaria mają tę samą tożsamość. Świeże dowody Linux/Windows mają dotyczyć dokładnie tej pary commitów, a treści v4 trafią do produkcji dopiero przez osobną, jawnie zatwierdzoną promocję.
|
||||
|
||||
CLI #38 zostało zmergowane jako `b0c789a`, ale prywatny receipt z Toolkit run `34743867441` dowodzi commitu sprzed squasha `c139ac5`. Publiczna bramka poprawnie odrzuciła wydanie. Badanie wykazało dwie dodatkowe luki: obecny CI tworzy commit wersji po testach, a receipty platform nie zawierają run/attempt, więc późniejszy producent może je przepisać.
|
||||
|
||||
Stan na 2026-09-13: 647 bazowych testów CLI przechodzi, typecheck, lint, buildy i smoke przechodzą po ponownym uruchomieniu poza sandboxem. npm latest to 1.20.0. Produkcyjny prefiks v4 w R2 jest pusty, polityka publikacji wyłączona, środowisko `content-production` nie istnieje. Backup obecnego v3 ma 57 obiektów i 35 776 634 bajty; różni się od historycznej fixture. Żadna naprawa nie została zaimplementowana.
|
||||
|
||||
## Co jest już ustalone
|
||||
|
||||
- **Wersja (wybór operatora):** finalna wersja jest commitowana w PR naprawczym; proponowana 1.21.0. Po testach nie powstaje żaden commit wersji.
|
||||
- **Świeże dowody (wybór operatora):** ponowny test zachowanych bajtów z udanego kanonicznego push/master przy testowanym SHA Toolkit, bez ponownego tłumaczenia treści.
|
||||
- **Źródło promocji:** tylko oryginalny push/master może być źródłem treści produkcyjnych; dispatch dowodowy nie buduje i nie autoryzuje wydania treści.
|
||||
- **Ograniczenia twarde:** ścisły SHA, pochodzenie z mastera, dowody obu systemów, sanityzacja publicznych receiptów, autoryzacja kursów, ochrona danych i harmonogram 2026-09-14T06:00:00Z nie podlegają wariantom.
|
||||
- **Granice procesu:** merge wykonuje człowiek; bez commitów na master, kasowania artefaktów, nadpisywania npm, włączania polityki i promocji w ramach przygotowania.
|
||||
|
||||
## Najważniejsze wybory na teraz
|
||||
|
||||
| Wybór | Dlaczego ma znaczenie | Rekomendacja |
|
||||
|---|---|---|
|
||||
| [D01 — granica dowodu PR i mastera](#d01) | Decyduje, ile przebiegów dowodowych potrzeba między mergem Toolkit a publikacją CLI. | A: osobny tryb PR i świeży dowód mastera po squashu. |
|
||||
| [D04 — skala automatyzacji sekwencji](#d04) | Łańcuch dwóch repozytoriów to największe źródło ręcznej pracy i pomyłek koordynacji. | A teraz; automatyczny łańcuch dopiero przy częstych wydaniach. |
|
||||
| [D06 — pakowanie npm](#d06) | Ustala, czy publikowane bajty są dokładnie tymi, które przeszły smoke. | A: jeden tarball z `gitHead`, przypięty npm. |
|
||||
| [D07 — dokończenie częściowego wydania](#d07) | Awaria po `npm publish` nie może prowadzić do drugiej publikacji ani przebudowy. | C na pierwsze wydanie, jeśli regresja „zero drugiej publikacji” pozostaje. |
|
||||
| [D08 — promocja v4 i zakres testów](#d08) | Oddziela gotowość pakietu od gotowości treści v4 i etapu 1 Session C. | A: osobne bramki, bez deklarowania gotowości v4 przed dowodami. |
|
||||
|
||||
Rekomendacje są proporcją między gwarancjami, automatyzacją i kosztem, a nie estymacją czasu ani zatwierdzeniem planu. Rekomendacje przeglądu planu nie są decyzjami operatora.
|
||||
|
||||
## Jak korzystać z kart
|
||||
|
||||
Każda karta opisuje sytuację, status i trzy warianty: **A — obecny plan**, **B — najsolidniejsza alternatywa**, **C — najzwinniejsza alternatywa**. Wariant C oszczędza zakres lub automatyzację, nigdy ścisłą tożsamość commitu, dowody obu systemów, prywatność ani ochronę danych.
|
||||
|
||||
Wcześniejsze wybory operatora (wersja w PR, ponowny test zachowanych bajtów) są opisane w statusach. Pole decyzji służy do utrwalenia wyboru albo świadomej korekty.
|
||||
|
||||
<a id="d01"></a>
|
||||
|
||||
## D01. Jak dowodzimy CLI przed mergem i po squashu
|
||||
|
||||
Toolkit master musi potwierdzić niezmergowany PR CLI, zanim operator go zmerguje, a po squashu powstaje nowy SHA, którego wcześniejszy dowód nie obejmuje. Bez rozdzielenia tych przypadków albo blokujemy wymagany check PR, albo ryzykujemy publikację z dowodem innego commitu. Wybór dotyczy liczby przebiegów i mechanizmu wyznaczania SHA.
|
||||
|
||||
**Status:** Przegląd planu dodał jawny `cli_candidate_kind` (`pr` / `release`); wybór mechanizmu nie jest decyzją operatora.
|
||||
|
||||
| Wariant | Podejście | Główna korzyść | Koszt / kompromis |
|
||||
|---|---|---|---|
|
||||
| A — obecny | Dispatch w trybie `pr` dla aktualnego head PR z tego samego repo, potem świeży dispatch `release` dla SHA mastera. | Dowód przed mergem i dokładny dowód wydawanego commitu. | Dwa przebiegi E2E i ponowna koordynacja po squashu. |
|
||||
| B — najsolidniejszy | Merge queue w CLI z dowodem dla commitu grupy; zgodność tego SHA z finalnym masterem wymaga osobnego potwierdzenia. | Możliwość ograniczenia powtórnych testów, jeśli finalny SHA pozostaje identyczny. | Nowa infrastruktura; bez dowodu zachowania SHA nadal potrzebny świeży test po mergu. |
|
||||
| C — najzwinniejszy | Dowód PR przez istniejącą ścieżkę Toolkit PR z `CLI_CANDIDATE_SHA`; nowy dispatch tylko w trybie `release`. | Mniej kodu trybów i walidacji wejść. | Niepotwierdzona wykonalność po mergu Toolkit: bez dowodu dla jego finalnego SHA wariant jest zablokowany. |
|
||||
|
||||
**Rekomendacja:** A; B rozważyć dopiero, gdy wydania CLI będą częste, a C tylko jeśli ścieżka PR Toolkit pozostaje dostępna po mergu repair.
|
||||
|
||||
**Do sprawdzenia:** W01–W02. [Szczegóły i uzasadnienie wariantów](decisions-details.md#d01).
|
||||
|
||||
**Twoja finalna decyzja:** C (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** …
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
<!-- decision-room: status=accepted; variant=C; basis=c154c1cf97e5; version=1; updated=2026-09-13T09:45:50.099Z -->
|
||||
|
||||
<a id="d02"></a>
|
||||
|
||||
## D02. Gdzie żyje tryb dowodowy w Toolkit
|
||||
|
||||
Ponowny test zachowanych bajtów potrzebuje przebiegu, który uruchamia E2E Linux/Windows, ale nie tłumaczy treści, nie wdraża Workera, nie publikuje pakietów i nie promuje R2. Kanoniczny `ci.yml` ma już te zadania obok zadań z efektami ubocznymi. Pomyłka w warunku zadania może więc uruchomić deploy z dispatchu.
|
||||
|
||||
**Status:** Operator wybrał ponowny test zachowanych bajtów; umieszczenie trybu w `ci.yml` to propozycja planu.
|
||||
|
||||
| Wariant | Podejście | Główna korzyść | Koszt / kompromis |
|
||||
|---|---|---|---|
|
||||
| A — obecny | `workflow_dispatch` tylko z mastera w `ci.yml`; zadania z efektami ubocznymi jawnie push-only; testy workflow. | Wspólna macierz E2E i walidatory bez duplikacji. | Każde nowe zadanie w `ci.yml` wymaga poprawnego warunku push-only. |
|
||||
| B — najsolidniejszy | Osobny `evidence.yml` wywołujący wspólną macierz E2E przez reusable workflow; brak zadań z efektami w pliku. | Strukturalna separacja: dispatch fizycznie nie ma dostępu do deployu. | Refaktor `ci.yml` na reusable workflow i nowe nazwy jobów w weryfikatorze. |
|
||||
| C — najzwinniejszy | Osobny mały workflow kopiujący tylko kroki E2E i receiptu, bez refaktoru `ci.yml`. | Separacja bez przebudowy głównego CI. | Duplikacja definicji E2E, która może się rozjechać z push/master. |
|
||||
|
||||
**Rekomendacja:** A z testem listującym wszystkie zadania z efektami; B, jeśli test workflow nie da się utrzymać jako wiarygodnej blokady.
|
||||
|
||||
**Do sprawdzenia:** W03. [Szczegóły i uzasadnienie wariantów](decisions-details.md#d02).
|
||||
|
||||
**Twoja finalna decyzja:** A (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** …
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
<!-- decision-room: status=accepted; variant=A; basis=62aa7f94824a; version=1; updated=2026-09-13T09:45:50.112Z -->
|
||||
|
||||
<a id="d03"></a>
|
||||
|
||||
## D03. Retencja stage, attempty i ponowienia
|
||||
|
||||
Dziś artefakty mają stałe nazwy, receipty nie zawierają run/attempt, a stage nie powstaje dla każdego commita mastera. Świeży dowód wymaga stage z dokładnego SHA Toolkit i jednoznacznego wyboru attemptu. Otwarte są koszt obsługi rerunów i ochrona przed wygaśnięciem artefaktów.
|
||||
|
||||
**Status:** Ponowny test zachowanych bajtów wybrany przez operatora; polityka rerunów i retencji to propozycja planu.
|
||||
|
||||
| Wariant | Podejście | Główna korzyść | Koszt / kompromis |
|
||||
|---|---|---|---|
|
||||
| A — obecny | Stage z każdego udanego mastera, nazwy z attemptem, świeży dispatch jako normalne ponowienie; rerun tylko z kompletem jobów tego attemptu. | Reruny działają, a selektywne kończą się odmową. | Walidacja kompletu attemptu to dodatkowa logika i testy. |
|
||||
| B — najsolidniejszy | A oraz archiwum stage poza retencją GitHub z hashem inventory i ostrzeżeniem przed wygaśnięciem. | Dowód i promocja nie zależą od terminu wygaśnięcia artefaktu. | Nowy prywatny magazyn, uprawnienia i proces jego utrzymania. |
|
||||
| C — najzwinniejszy | Świeże dowody dispatch akceptowane tylko dla attempt 1; każde ponowienie dowodów to nowy dispatch. Retencja stage i ścisła walidacja attemptu źródłowego pozostają. | Mniej obsługiwanych stanów dowodowego dispatch. | Flaky E2E wymaga pełnego nowego przebiegu; walidacji oryginalnego push/master nie można pominąć. |
|
||||
|
||||
**Rekomendacja:** C, jeśli koszt pełnego E2E jest akceptowalny; A, gdy reruny okażą się częste. B dopiero przy wygaśnięciu potrzebnego stage.
|
||||
|
||||
**Do sprawdzenia:** W04–W05. [Szczegóły i uzasadnienie wariantów](decisions-details.md#d03).
|
||||
|
||||
**Twoja finalna decyzja:** A (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** …
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
<!-- decision-room: status=accepted; variant=A; basis=e5d8c40005ca; version=1; updated=2026-09-13T09:45:50.114Z -->
|
||||
|
||||
<a id="d04"></a>
|
||||
|
||||
## D04. Ile sekwencji wydania automatyzujemy
|
||||
|
||||
Po mergach trzeba kolejno: pobrać aktualne SHA, uruchomić dowód, zarejestrować run, uruchomić wydanie CLI i sprawdzić pakiet. GitHub serializuje przebiegi tylko w obrębie jednego repozytorium, więc koordynację między repo wykonuje Session A. Wybór dotyczy podziału pracy między operatora a automatyzację.
|
||||
|
||||
**Status:** Plan zakłada ręcznie wyzwalane dispatch z jawnymi wejściami i inspekcją przed każdą operacją; skala automatyzacji jest otwarta.
|
||||
|
||||
| Wariant | Podejście | Główna korzyść | Koszt / kompromis |
|
||||
|---|---|---|---|
|
||||
| A — obecny | Session A uruchamia dispatch z jawnymi SHA i run/attempt, grupy concurrency bez anulowania, inspekcja runów i zmiennych przed każdym krokiem. | Czytelna odpowiedzialność i brak tokenów cross-repo. | Kilka ręcznych kroków i zależność od dyscypliny operatora. |
|
||||
| B — najsolidniejszy | Automatyczny łańcuch: sukces mastera Toolkit wyzwala dowód, receipt wyzwala wydanie CLI; lease między repo. | Powtarzalna sekwencja bez ręcznego przepisywania identyfikatorów. | Token cross-repo, nowe uprawnienia i trudniejsze zatrzymanie łańcucha. |
|
||||
| C — najzwinniejszy | Jak A, ale ścieżka wydania nie używa zmiennych repozytorium; zmienne zostają tylko wskaźnikiem dla checków PR. | Mniej mutowalnego stanu do inspekcji. | Wejścia dispatch trzeba przepisywać ręcznie z dowodu. |
|
||||
|
||||
**Rekomendacja:** A teraz, z C jako uproszczeniem, jeśli checki PR CLI nie wymagają aktualizacji zmiennych po squashu; B przy regularnych wydaniach.
|
||||
|
||||
**Do sprawdzenia:** W02, W06. [Szczegóły i uzasadnienie wariantów](decisions-details.md#d04).
|
||||
|
||||
**Twoja finalna decyzja:** B (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** …
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
<!-- decision-room: status=accepted; variant=B; basis=7142d35e7877; version=1; updated=2026-09-13T09:45:50.103Z -->
|
||||
|
||||
<a id="d05"></a>
|
||||
|
||||
## D05. Jak dalej obsługujemy numer wersji
|
||||
|
||||
Obecny CI oblicza wersję i commituje ją po testach, przez co publikowany commit nie jest testowanym commitem. Operator wybrał wersję w PR naprawczym. Pozostaje, co zostaje z `auto-version.mjs` i jak ma wyglądać przygotowanie wersji w kolejnych wydaniach.
|
||||
|
||||
**Status:** Operator wybrał wersję commitowaną w PR naprawczym (proponowana 1.21.0); los `auto-version.mjs` i proces kolejnych wydań to propozycja planu.
|
||||
|
||||
| Wariant | Podejście | Główna korzyść | Koszt / kompromis |
|
||||
|---|---|---|---|
|
||||
| A — obecny | Wersja w `package.json` w PR; CI tylko waliduje; `auto-version.mjs` zostaje jako rekomendacja bez zapisu. | Człowiek przegląda wersję, a narzędzie podpowiada bump. | Ręczny krok w każdym PR wydaniowym. |
|
||||
| B — najsolidniejszy | Automatyczny PR wersji (np. usługa typu release-please) przygotowuje bump i changelog do review. | Mniej ręcznej pracy przy regularnych wydaniach. | Nowa usługa, poza zakresem naprawy; kolejny writer w repo. |
|
||||
| C — najzwinniejszy | Usunąć ścieżkę zapisu i rekomendacji z `auto-version.mjs`; ręczny bump i walidacja zgodności z tagiem oraz npm. | Najmniej kodu i jedno źródło prawdy. | Brak podpowiedzi bumpu na podstawie commitów. |
|
||||
|
||||
**Rekomendacja:** C lub A, zależnie od tego, czy podpowiedź bumpu jest używana; B jako osobna zmiana w przyszłości.
|
||||
|
||||
**Do sprawdzenia:** W07. [Szczegóły i uzasadnienie wariantów](decisions-details.md#d05).
|
||||
|
||||
**Twoja finalna decyzja:** A (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** chcę za wszelką cenę uniknąc manualnego akceptowania wersji, jeżeli możemy to utrzymać w podobnym stanie jak działało lub inaczej zapewnić automatyzację bez wzrostu złożoności - zróbmy to
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
<!-- decision-room: status=accepted; variant=A; basis=1ca96b22e95b; version=1; updated=2026-09-13T09:45:50.116Z -->
|
||||
|
||||
<a id="d06"></a>
|
||||
|
||||
## D06. Jak pakujemy i publikujemy npm
|
||||
|
||||
Publikacja katalogu przez npm sama uzupełnia `gitHead`, ale publikacja gotowego tarballa już nie. Plan chce spakować raz, przetestować ten tarball i opublikować dokładnie te bajty. Wybór określa, czy smoke dotyczy publikowanych bajtów, czy tylko ich odpowiednika.
|
||||
|
||||
**Status:** Przegląd planu dodał katalog pakowania z `gitHead` i przypięty npm 11.12.1; operator tego nie zatwierdzał.
|
||||
|
||||
| Wariant | Podejście | Główna korzyść | Koszt / kompromis |
|
||||
|---|---|---|---|
|
||||
| A — obecny | Oddzielny katalog pakowania z `gitHead` walidowanego SHA, przypięty npm 11.12.1, jeden tarball: smoke, retencja, publikacja, weryfikacja rejestru. | Opublikowane bajty to dokładnie bajty po smoke. | Własny helper pakowania i test mock-registry. |
|
||||
| B — najsolidniejszy | A oraz npm provenance z OIDC i porównanie powtórnego pakowania. | Publiczny, weryfikowalny związek pakietu z workflow i commitem. | Wymaga zgodności provenance z publikacją tarballa i konfiguracji uprawnień. |
|
||||
| C — najzwinniejszy | Publikacja katalogu z checkoutu dokładnego SHA; lokalny `npm pack` i porównanie integrity z rejestrem po publikacji. | npm sam ustawia `gitHead`; mniej kodu. | Rozbieżność wykryjemy dopiero po publikacji, której nie da się nadpisać. |
|
||||
|
||||
**Rekomendacja:** A; B jako rozszerzenie, gdy potwierdzimy zgodność provenance; C nie daje gwarancji przed publikacją.
|
||||
|
||||
**Do sprawdzenia:** W08–W09. [Szczegóły i uzasadnienie wariantów](decisions-details.md#d06).
|
||||
|
||||
**Twoja finalna decyzja:** C (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** …
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
<!-- decision-room: status=accepted; variant=C; basis=c29071bc49ba; version=1; updated=2026-09-13T09:45:50.105Z -->
|
||||
|
||||
<a id="d07"></a>
|
||||
|
||||
## D07. Jak kończymy częściowo udane wydanie
|
||||
|
||||
Jeśli npm przyjmie wersję, a upload binariów do GitHub Release się nie uda, ponowny run nie może zbudować pakietu od nowa ani wywołać drugiego `npm publish`. Obecne pobieranie artefaktów widzi tylko bieżący run. Wybór dotyczy automatyzacji rzadkiej, ale kosztownej awarii.
|
||||
|
||||
**Status:** Automatyczny recovery z oryginalnym run/attempt to poprawka z przeglądu planu; operator nie wybrał jeszcze skali.
|
||||
|
||||
| Wariant | Podejście | Główna korzyść | Koszt / kompromis |
|
||||
|---|---|---|---|
|
||||
| A — obecny | Dispatch recovery z oryginalnym run/attempt i ID artefaktów; manifest wydania; porównanie tarballa w rejestrze; dokończenie tylko brakujących assetów. | Powtarzalne dokończenie bez drugiej publikacji. | Dodatkowy workflow i testy dla rzadkiego scenariusza. |
|
||||
| B — najsolidniejszy | Kolejność: szkic GitHub Release z kompletem binariów, potem npm, na końcu upublicznienie; recovery jak A. | Mniej stanów częściowych w ogóle. | Przebudowa kolejności zadań i obsługa porzuconych szkiców. |
|
||||
| C — najzwinniejszy | Workflow odmawia publikacji istniejącej wersji; runbook opisuje ręczne dokończenie z weryfikacją integrity i hashy binariów. | Brak workflow recovery; zachowana blokada drugiej publikacji. | Ręczna praca operatora i ryzyko pomyłki przy uploadzie. |
|
||||
|
||||
**Rekomendacja:** C na pierwsze wydanie z regresją „zero drugiej publikacji”; A, gdy runbook zostanie użyty lub wydania staną się częste.
|
||||
|
||||
**Do sprawdzenia:** W10. [Szczegóły i uzasadnienie wariantów](decisions-details.md#d07).
|
||||
|
||||
**Twoja finalna decyzja:** C (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** …
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
<!-- decision-room: status=accepted; variant=C; basis=8c7b00733ce1; version=1; updated=2026-09-13T09:45:50.107Z -->
|
||||
|
||||
<a id="d08"></a>
|
||||
|
||||
## D08. Promocja v4 i zakres testów przed jej bramką
|
||||
|
||||
Opublikowany pakiet nie oznacza, że v4 jest dostępne: R2 v4 jest puste, polityka wyłączona, a moduł 1 odblokuje się 2026-09-14T06:00:00Z. Etap 1 Session C wymaga dowodu faktycznego dostarczenia. Wybór dotyczy tego, które testy zamykają wydanie CLI, a które czekają na promocję.
|
||||
|
||||
**Status:** Plan rozdziela wydanie CLI od promocji treści; zakres testów przypisanych do każdej bramki jest otwarty.
|
||||
|
||||
| Wariant | Podejście | Główna korzyść | Koszt / kompromis |
|
||||
|---|---|---|---|
|
||||
| A — obecny | Po publikacji: pobrany pakiet, instalacja anonimowa, potem zatwierdzony login i pełna akceptacja v3; promocja v4 osobno, potem test tożsamości v4 i odblokowania. | Wczesny dowód pakietu bez czekania na treści. | Dwie serie testów z realnym kontem. |
|
||||
| B — najsolidniejszy | A oraz próba promocji i wycofania w osobnym buckecie przed zatwierdzeniem produkcyjnej promocji. | Mechanika promocji sprawdzona przed produkcją. | Środowisko testowe i dodatkowe dane. |
|
||||
| C — najzwinniejszy | Po publikacji tylko instalacja anonimowa i integrity; login, v3 i v4 w jednej akceptacji po promocji. Weryfikacja wydania pozostaje otwarta do tych testów. | Jedna wspólna sesja akceptacyjna. | Regresja v3 wyjdzie później; brak podstaw do zamknięcia weryfikacji CLI lub etapu 1 Session C wcześniej. |
|
||||
|
||||
**Rekomendacja:** A; B nie jest konieczne, jeśli promocja używa istniejącego `promote-content.yml` z CAS i withdraw.
|
||||
|
||||
**Do sprawdzenia:** W11–W14. [Szczegóły i uzasadnienie wariantów](decisions-details.md#d08).
|
||||
|
||||
**Twoja finalna decyzja:** A (zaakceptowano)
|
||||
|
||||
**Uzasadnienie, akceptowane ograniczenia i warunki:** …
|
||||
|
||||
**Data / osoba:** 2026-09-13 / Marcin Czarkowski
|
||||
<!-- decision-room: status=accepted; variant=A; basis=4fc752bec70b; version=1; updated=2026-09-13T09:45:50.110Z -->
|
||||
|
||||
## Co trzeba sprawdzić przed realizacją i wydaniem
|
||||
|
||||
Większość niewiadomych to testy i inspekcje po stronie Session A, nie pytania do operatora. Pełny rejestr W01–W14 jest w [załączniku](decisions-details.md#verification).
|
||||
|
||||
| Obszar | Co pozostaje niewiadome | Kiedy potrzebujemy odpowiedzi |
|
||||
|---|---|---|
|
||||
| Dowód PR i mastera | Pełny cykl merge Toolkit → dowód PR CLI → squash → świeży dowód mastera. | Przed review PR naprawczych. |
|
||||
| Workflow Toolkit | Czy dispatch nie uruchamia żadnego zadania z efektami i czy każdy master zachowuje stage. | Przed mergem Toolkit. |
|
||||
| Attempty | Odmowa przepisanych, mieszanych i niekompletnych attemptów. | Przed mergem Toolkit. |
|
||||
| Pakiet | `gitHead`, integrity, wersja 1.21.0 wolna, brak drugiej publikacji. | Przed mergem CLI i ponownie przed publikacją. |
|
||||
| Produkcja | Akceptacja pobranego pakietu, prerekwizyty promocji, odblokowanie po 06:00 UTC. | Po publikacji, przed promocją i po granicy czasu. |
|
||||
|
||||
## Koszty i ograniczenia, które warto zaakceptować świadomie
|
||||
|
||||
- Świeży dowód po squashu oznacza drugi pełny przebieg E2E Linux/Windows dla każdego wydania.
|
||||
- Stage z każdego udanego mastera zwiększa zużycie retencji artefaktów GitHub.
|
||||
- Ręczna koordynacja między repozytoriami zależy od inspekcji Session A przed każdą operacją; zmienne nie są blokadą.
|
||||
- Wydanie pakietu nie czyni v4 dostępnym; etap 1 Session C pozostaje niespełniony do dowodu dostarczenia.
|
||||
- Obecny v3 różni się od historycznej fixture; zachowujemy faktyczny stan, nie przywracamy fixture.
|
||||
|
||||
## Dyspozycja po przeglądzie
|
||||
|
||||
**Zakres pierwszego wydania:** …
|
||||
|
||||
**Wybrane warianty do przeniesienia do planu:** …
|
||||
|
||||
**Decyzje odroczone i warunki powrotu:** …
|
||||
|
||||
**Najważniejsze dowody do zebrania przed kodowaniem:** …
|
||||
|
||||
**Pozostałe uwagi:** …
|
||||
|
||||
Szczegółowe koszty i zależności wariantów są w [załączniku](decisions-details.md). Ten brief nie zmienia planu ani nie autoryzuje implementacji.
|
||||
@@ -0,0 +1,32 @@
|
||||
# Implementation evidence — Session A
|
||||
|
||||
Approved starting plan SHA-256: `f46f2c7789a2a1844af076198ceb3c9d2dae877a7a465cdd4ffce5ef4168fb59`. Operator invoked `10xWorkflow/10x-goal-implement` on 2026-09-13. Canonical execution state remains exclusively in plan.md Progress. This file records commands and observations, not a competing state tracker.
|
||||
|
||||
## Setup evidence
|
||||
|
||||
Fresh fetch retained CLI b0c789af70f30255fb05149ad86f8534f96c2cb6 and Toolkit39925ab6155c9c17fbdabd69d160b5f4fe928c4e. No unrelated code edits in either dedicated worktree. CLI roadmap absent; synchronization skipped. All required CLI/package scripts and tools exist. Toolkit companion dependency installation used pnpm10.33.0 frozen lockfile and completed successfully. Its existing optional internal-pkg postinstall could not find not-yet-built dist/install.mjs and tolerated it by its existing script; builds remain required. Test commands use Node22.14.0, Vitest3.2.4 and pinned Wrangler4.80.0.
|
||||
|
||||
## Phase 1 test-first reproduction
|
||||
|
||||
Before production changes, root ran in Toolkit companion:
|
||||
|
||||
```sh
|
||||
env PATH=/Users/admin/.nvm/versions/node/v22.14.0/bin:$PATH pnpm --filter @przeprogramowani/api exec vitest run scripts/__tests__/public-coordinated-receipt.test.mjs
|
||||
```
|
||||
|
||||
2026-09-13 12:28 Warsaw: **14 PASS, 1 FAIL**. New regression `cannot relabel retained platform evidence as a different run or attempt` failed with `expected [Function] to throw an error`: the current producer accepted the identical original OS receipts when runAttempt changed to2. This is the expected RED proof, not a failed repaired-code gate. Root then authorized the phase subagent to implement the strict identity changes. Gate-stack/green/break-check/commit evidence will follow only after actual execution.
|
||||
|
||||
Read-only GitHub attempt-job API for retained Toolkit34743867441/1 confirmed real run_id/run_attempt/head_sha fields, and retained-stage artifact metadata supplied workflow_run identity and archive digest. No candidate variables, workflow dispatches or production resources were changed during this inspection.
|
||||
|
||||
## Phase 1 completed gates
|
||||
|
||||
- Toolkit selected receipt/stage/input/workflow gates: 47 PASS, including 9 workflow checks. Initial run had two collection errors because course-content dist was absent; ordinary `pnpm --filter @przeprogramowani/course-content build` resolved the prerequisite (self-fix 1/2), then all passed.
|
||||
- CLI coordinated receipt gate: 36 PASS. Actual historical GitHub ZIP parsed with the new bounded in-memory reader; schema1 proof was not accepted as a new release.
|
||||
- Deliberate-break: root staged exact touched paths, replaced producer identity comparison with false in the worktree, observed 3 expected failures including attempt relabeling, and unconditionally restored from index. Restored file had no unstaged diff.
|
||||
- CLI full isolated unit suite: 657 PASS/0 FAIL; typecheck PASS; lint PASS (0 errors, 3 existing warnings).
|
||||
- Toolkit Node22 `CI=true pnpm ci:local`: PASS, including format/lint/build, API616 + course-content150 + internal61 + artifact16 + shared-reference25 tests, and all content/reference validators.
|
||||
- Both staged diffs pass whitespace checks. Original export/applied-decision hashes unchanged. No remote variables, dispatches or production writes.
|
||||
|
||||
Minor adaptations: helper modules make snapshot/source/artifact guards executable in tests; ordinary push uses build eligibility with vetted master ancestors while release dispatch requires current master pair. Individual validated artifact-ID downloads flatten one archive only. Lease generation uses full Git SHA. Original-stage receipts use null source artifact ID, retained dispatch requires original exact ID. Promotion consumer migration remains Phase3; Phase2 must supply explicit release attempt/artifact/lease.
|
||||
|
||||
Private gate logs: phase1-cli-{unit,typecheck,lint}.log, phase1-toolkit-ci.log, phase1-break.log under the established private evidence directory.
|
||||
@@ -0,0 +1,57 @@
|
||||
# Naprawa wydań CLI — plan po decyzjach operatora
|
||||
|
||||
[Pełny plan](plan.md) · [Eksport decyzji](decisions-record.md) · [Badanie wykonalności](decision-alignment-research.md) · [Handoff](release-handoff.md)
|
||||
|
||||
## Co i dlaczego
|
||||
|
||||
Naprawiamy wydanie po squashu: dowód ma wskazywać dokładnie commit publikowany jako npm i binaria. Operator wybrał automatyzację numerowania i łańcucha wydań. Nie będzie osobnego zatwierdzania numeru wersji; pozostaje zwykły merge kodu przez człowieka.
|
||||
|
||||
## Punkt wyjścia
|
||||
|
||||
CLI master b0c789a i Toolkit master 39925ab nie zmieniły się po świeżym fetch. Stary dowód obejmuje CLI sprzed squasha. Obecny automat dodatkowo tworzy commit wersji po testach. Naprawa nie została zaimplementowana; wcześniejsze 647 testów to wynik bazowego kodu. Ostatnia inspekcja wykazała npm 1.20.0 i nieopublikowane v4; ponowimy ją przed operacjami.
|
||||
|
||||
## Docelowe zachowanie
|
||||
|
||||
Bot oblicza wersję i zapisuje ją w istniejącym PR-ze CLI przed końcowymi testami. Po ludzkich mergach koordynator sam uruchamia świeże testy zachowanych treści na Linux/Windows, sprawdza ich tożsamość i wyzwala publikację dokładnego commitu. Pobranie rzeczywistej paczki z npm zamyka weryfikację jej bajtów; udany build nie wystarcza.
|
||||
|
||||
## Decyzje
|
||||
|
||||
| ID | Wybór | Skutek | Źródło |
|
||||
|---|---|---|---|
|
||||
| D01 C | Obecna ścieżka PR Toolkit; dispatch tylko wydaniowy | Najpierw merge CLI, potem Toolkit; przyszły samodzielny PR CLI potrzebuje rzeczywistego PR-a Toolkit | Eksport |
|
||||
| D02 A | Tryb dowodowy w ci.yml | Wspólne testy; deploy/publish/transformacje pozostają push-only | Eksport |
|
||||
| D03 A | Pełna obsługa attemptów | Nowe nazwy artefaktów, ścisły wybór próby, komplet obu OS; bez kasowania starych | Eksport |
|
||||
| D04 B | Automatyczny łańcuch i trwała blokada | Jeden koordynator, reakcja na oba repo, jawna tożsamość operacji i kontrolowane uprawnienia | Eksport |
|
||||
| D05 A + komentarz | Automatyczny numer w tym samym PR-ze | Istniejące reguły bump; brak osobnego PR-a lub akceptacji wersji | Komentarz operatora nadrzędny wobec starej rekomendacji |
|
||||
| D06 C | Publikacja katalogu | npm sam ustawia registry gitHead; porównanie integrity następuje po publikacji | Eksport |
|
||||
| D07 C | Ręczne dokończenie awarii częściowej | Runbook i zachowane artefakty; nigdy drugi npm publish tej samej wersji | Eksport |
|
||||
| D08 A | Osobne testy paczki i treści | Najpierw pobrany npm/v3, później zatwierdzona promocja i weryfikacja v4 | Eksport |
|
||||
|
||||
## Zakres i mechanizm
|
||||
|
||||
D05 realizujemy przez mały automat przygotowujący wersję w zwykłym PR-ze. Numer wynika z ostatniego opublikowanego taga i reguł conventional commits; kolejne uruchomienie nie podbija go ponownie. Obecne dane dają 1.21.0, lecz nie jest to numer do ręcznego zatwierdzenia. Automat działa z zaufanego kodu mastera i nie uruchamia skryptów kandydata z tokenem zapisu.
|
||||
|
||||
Koordynator działa w Toolkit. Własność operacji zapisuje na osobnej referencji Git poza masterem, aktualizowanej bez force; blokada obejmuje testy i publikację w obu repo. Potrzebne są tokeny o wskazanych w planie zakresach, w tym odczyt blokady Toolkit przez zaufany workflow CLI. Koordynator domyślnie pozostaje nieaktywny do weryfikacji konfiguracji przez Session A. Nie dodajemy bazy ani nowej usługi. Opóźnione, powtórzone lub niejednoznaczne zdarzenia nie mogą wywołać drugiego wydania.
|
||||
|
||||
Po npm pack i smoke następuje npm publish katalogu z tego samego czystego checkoutu. To ponowne pakowanie: równość bajtów sprawdzamy pobraniem z rejestru. Próba syntetyczna npm 11.12.1 przeszła; test rzeczywistej paczki nadal jest wymagany. Niezgodność oznacza nieukończone wydanie, którego nie nadpisujemy.
|
||||
|
||||
W zakresie są workflowy, walidatory, regresje, dokumentacja i dwa PR-y. Nie zmieniamy produktu, autoryzacji, zegarów, uprawnień kursantów ani EDU. Ukończenie auth-access-resilience nie jest nową zależnością. Zachowujemy cutoff v3, pełne foldery skills i odblokowanie modułu 1 o 2026-09-14 06:00 UTC.
|
||||
|
||||
## Fazy
|
||||
|
||||
| Faza | Wynik | Główne ryzyko |
|
||||
|---|---|---|
|
||||
| 1 | Świeże dowody z zachowanych bajtów i ścisłe attempty | Pomieszanie źródłowego runu z nowym testem |
|
||||
| 2 | Automatyczna wersja/koordynacja i publikacja katalogu | Uprawnienia, wyścigi, niezgodność po npm publish |
|
||||
| 3 | Testy, runbooki, review i dwa PR-y | Kolejność merge oraz bezpieczne uruchomienie koordynatora |
|
||||
| 4 | Faktyczne wydanie i pobrana paczka | Login, oddzielna promocja treści i rzeczywisty czas odblokowania |
|
||||
|
||||
Przed implementacją potrzebne jest zatwierdzenie wynikowego planu. Przed aktywacją automatu sprawdzimy zakresy credentiali i konkurencyjne zapisy na izolowanej referencji testowej. Po wdrożeniu tej naprawy zwykły merge CLI nadal wymaga dowodu z realnego PR-a Toolkit — D01 C nie rozwiązuje niezależnych PR-ów CLI bez takiego partnera.
|
||||
|
||||
## Odbiór i pozostałe bramki
|
||||
|
||||
- Dowody, tag, registry gitHead, wersja i binaria wskazują ten sam commit; integrity pobranej paczki odpowiada wcześniejszemu pack.
|
||||
- Faktyczna paczka przechodzi login/v3, EN/PL, pełne skills, sync/refresh i ochronę lokalnych zmian. Phase 4: najwyższy dostępny autoryzowany kurs, stabilny v3, brak zapisów w podglądzie i brak bindingu po nieudanym preflight.
|
||||
- Promocja v4 wymaga osobnej zgody i procedury withdraw; później weryfikujemy tożsamość i prawdziwe odblokowanie. Session C stage 1 pozostaje niespełniony.
|
||||
|
||||
Eksport już zastosowano. Nie trzeba ponownie eksportować decyzji. Ten dokument służy do odbioru poprawionego planu, nie autoryzuje implementacji ani produkcji.
|
||||
@@ -0,0 +1,251 @@
|
||||
# Repair exact-commit CLI release evidence
|
||||
|
||||
## Overview
|
||||
|
||||
Revision aligned to the operator's exported Decision Record on 2026-09-13. **Planning only; implementation is not approved.** The authoritative export SHA-256 is `792e4b3ba300d81e5db00f5e12dbc899eb585d6ba27639fc4411ffc871d1ddc6`. The coordinator already applied all eight decisions (saved 8, conflict 0, rejected 0); this revision neither reapplies the record nor edits decision-owner fields.
|
||||
|
||||
Canonical implementation Progress lives only here. The operator permits this planning stage to finish with the corrected plan and independent review. `10x-goal-implement`, PR implementation, publishing and production changes require subsequent authorization. Export is not that authorization.
|
||||
|
||||
## Current State Analysis
|
||||
|
||||
Fresh fetch on 2026-09-13 still gives CLI `b0c789af70f30255fb05149ad86f8534f96c2cb6` and Toolkit `39925ab6155c9c17fbdabd69d160b5f4fe928c4e`. The private successful run `34743867441/1` proves pre-squash CLI `c139ac5d89bc935be67174cc031e96f62661036d`; CLI run `34745146982/1` correctly rejects it. OS receipts omit run/attempt, artifact names are fixed, and the current version job creates an untested master commit after the evidence gate.
|
||||
|
||||
The earlier production inspection found no v4 objects/pointer/lock, disabled publication policy and npm latest 1.20.0. Those are timestamped observations in [handoff](release-handoff.md), not renewed production assertions. Full baseline tests were 647 PASS; no repaired implementation exists. Read-only branch-protection and rules queries returned no rules in either repository; the human-merge boundary is an operator instruction, not an inferred GitHub protection guarantee.
|
||||
|
||||
Source findings: CLI `ci.yml` accepts Toolkit PR evidence before CLI merge; Toolkit `ci.yml` checks out the exact transient CLI candidate without requiring it on master. `auto-version.mjs` currently anchors to candidate package.version and is not repeat-safe on a PR. Its existing Angular bump rules calculate **1.21.0** from v1.20.0 and #38; this is a computed example, not a human version choice. `src/index.ts` embeds package.json version at build time. A synthetic npm 11.12.1 loopback-registry experiment confirmed directory publish adds registry gitHead and can produce bytes identical to prior npm pack; the real package still needs regression and registry verification.
|
||||
|
||||
## Desired End State
|
||||
|
||||
The operator merges code PRs as usual without choosing or separately approving version numbers. Automatic preparation commits the computed version to the same CLI PR before its final tests. An automatic cross-repository coordinator then obtains fresh Linux/Windows evidence for the exact human-merged pair and publishes the CLI directory from that exact SHA. Tag, registry gitHead, package version and binaries identify that tested commit; post-publish download proves integrity against the pre-publish pack. A mismatch is an incomplete release, never success or a reason to republish.
|
||||
|
||||
Fresh evidence re-tests retained bytes from a successful canonical Toolkit push/master at the same Toolkit SHA. Receipt run/attempt and source-stage run/attempt remain distinct and strict. Public logs/downloads contain only bounded sanitized receipts and public release manifests. No old artifact is deleted or relabeled. Production content approval and actual downloaded-package v4 acceptance remain separate gates; Session C stage 1 remains unsatisfied until its delivery evidence exists.
|
||||
|
||||
## Accepted decisions and scope
|
||||
|
||||
| Decision | Final selection and interpretation |
|
||||
|---|---|
|
||||
| D01 C | Existing Toolkit PR path proves exact transient PR pairs; new evidence dispatch is release-only. Bootstrap merges CLI first, then Toolkit. No dispatch PR mode or equivalent-tree exception. |
|
||||
| D02 A | Evidence dispatch stays in canonical Toolkit ci.yml; transformation, deploy, publish and notifications remain push-only. Orchestration is a separate trusted workflow. |
|
||||
| D03 A | Retain a stage for every successful Toolkit master, use attempt-specific artifacts and require the complete selected attempt. Fresh dispatch is the normal retry; complete reruns remain supported. |
|
||||
| D04 B | Automatic chain with a durable cross-repository lease; react to either CLI master advancement or successful Toolkit master CI. Session A owns bootstrap/activation and all manual coordination; automation becomes the sole runtime writer after handover. |
|
||||
| D05 A + comment | Automate the version in the same code PR using existing bump rules. No separate version PR, numerical approval or release-time master commit. The operator comment takes precedence over the card's earlier manual-version recommendation. |
|
||||
| D06 C | Publish the directory from exact tested checkout with pinned npm; retain a pre-publish pack and compare the actual registry bytes afterward. Accept detection after publication rather than the former single-tarball prevention guarantee. |
|
||||
| D07 C | Refuse any second npm publish of an existing version. Retain evidence and document manual completion of missing GitHub assets; no automatic recovery workflow. |
|
||||
| D08 A | Verify downloaded npm/v3 after publication and approved login; promote content separately, then verify v4 identity and real-time unlock. |
|
||||
|
||||
D05 authoritative comment: “chcę za wszelką cenę uniknąc manualnego akceptowania wersji, jeżeli możemy to utrzymać w podobnym stanie jak działało lub inaczej zapewnić automatyzację bez wzrostu złożoności - zróbmy to”. Moving the existing writer before the human code merge is the smallest coherent interpretation. Literal zero new logic is incompatible with removing the current identity defect; reuse the current bump library instead of adding a release service. A normal code merge includes the bot's version change, but there is no extra approval step for that number.
|
||||
|
||||
**Scope:** release workflows/helpers/tests, automatic same-PR version preparation, central release coordination/lease, documentation/contracts/lessons, two repair PRs and subsequent authorized evidence. No course-selection/auth/entitlement/clock/curriculum changes, no EDU actions, no R2 publisher redesign, no merge queue, no auto-merge, no direct master writer, no automatic content promotion, no external archive service. auth-access-resilience completion/EDU commit 71882433 is coordination information only and adds no prerequisite.
|
||||
|
||||
## Implementation approach
|
||||
|
||||
### D01 C: exact PR proof and reversed bootstrap
|
||||
|
||||
Freeze CLI PR head Cpr after automatic version preparation. Toolkit repair PR head Tpr tests Cpr through its existing CLI_CANDIDATE_SHA path. CLI verifies that exact same-repository Toolkit PR run and both platforms. These are transient candidates, never permanent master pins. Human merges CLI first to Cm. Its old Cpr receipt cannot release Cm; publication waits/fails closed while the Toolkit workflow prerequisite is absent. Re-test Toolkit PR Tpr against Cm, then human merges Toolkit to Tm. A successful push/master at Tm retains its stage; release-only evidence dispatch re-tests it against Cm.
|
||||
|
||||
For later CLI-only work, D01 C requires a **real live Toolkit companion PR** for pre-merge proof. Do not manufacture no-op PRs, resurrect merged PRs, disable the check, or silently add dispatch PR mode. That limitation is part of this selected design. If CLI-only releases without such a companion become required, return for a targeted D01 decision; the current paired repair has a feasible path.
|
||||
|
||||
### D05: deterministic same-PR version preparation
|
||||
|
||||
Reuse/refactor auto-version.mjs into calculation and explicit write entry points. Pin baseline to the latest successfully published stable release/tag on fetched master, validate tag/registry gitHead and immutable baseline, and calculate from that baseline through candidate head. Never use a bot-mutated candidate package.version as the baseline. Exclude generated version-only commits and version-only package.json differences from release detection; preserve existing major/minor/patch rules and evaluate package-affecting paths. Add skills/ and build/public-package inputs to the explicit release-input audit if existing src/package.json filtering omits shipped changes; do not infer release solely from docs churn.
|
||||
|
||||
A trusted base-branch workflow handles same-repository PR events and base advancement. It reads PR Git objects as data, executes only trusted master scripts/dependencies in a separate directory, and writes only the calculated package.json version to that same PR head with an expected-head check and fast-forward update. Reject forks, closed PRs and stale head/base; never execute/install candidate code with a write token or force-push. Use scoped RELEASE_TOKEN (or its scoped replacement) so the new commit triggers ordinary CI; do not rely on GITHUB_TOKEN event recursion. Idempotent repeat is a no-op; changed head/base/release baseline invalidates preparation and previous exact-pair evidence. Validate again at merge/release: stale version means stop and automatically prepare a follow-up ordinary code PR only if separately authorized; this repair never silently creates or merges one.
|
||||
|
||||
Before the new workflow exists on master, Session A runs the same automatic calculation while preparing the initial repair PR. This bootstrap does not ask the operator to select 1.21.0. The normal human code merge remains mandatory. Record source head, base, release tag/SHA and calculation result; no commits after the final proof can reuse that proof.
|
||||
|
||||
Squash can change commit-message history as well as SHA. Release validation uses the version-preparation record for the exact merged PR head, verifies GitHub's merged-commit relationship and the committed version in Cm, and requires the same published baseline. It does not recompute a potentially different bump from the squash message and silently rewrite the version. Fresh Cm evidence remains independently mandatory; the preparation record is numbering provenance, not a substitute for source identity.
|
||||
|
||||
The bounded preparation record names canonical trusted prepare-version.yml run/attempt, input PR head, resulting version-prepared PR head, base SHA, published baseline version/tag/SHA, calculated number and exact artifact ID. Verify successful producer jobs, current attempt and all identities before using it; reject a record supplied by PR code or mismatched artifact. Initial repair bootstrap, before that workflow exists on master, records the same calculation and inputs in a retained CI artifact and verifies the calculation again from the newly trusted master helper after the human merge, against the original PR Git objects. No pre-merge local file alone authorizes a release.
|
||||
|
||||
Version preparation also reconciles open PRs after a verified successful CLI release advances the published baseline, even without a new head/base commit. Use trusted CI workflow completion with validated release result, not an assumed release-event recursion from GITHUB_TOKEN. Include a regression for this ordering; no-op calculations cannot create bot loops.
|
||||
|
||||
### D02/D03: fresh evidence and retained stages
|
||||
|
||||
Add release-only workflow_dispatch to Toolkit ci.yml, on master. Inputs carry exact CLI/Toolkit SHAs, original successful push/master run and attempt, stage artifact ID, release ID/hash, and coordinator lease generation. Validate workflow SHA = requested Toolkit SHA = retained-stage source and require both requested source SHAs on fetched master; release requests must still match the selected current master pair before publication. Snapshot inputs once; don't reread mutable candidate vars in later jobs.
|
||||
|
||||
Dispatch downloads and verifies retained bytes and exact archive membership, reruns Linux/Windows E2E/OpenAPI and produces new OS/public receipts. It cannot transform content, publish packages, deploy Workers, promote R2 or send notifications. Ordinary Toolkit PR/push tests remain available; their producer receipts bind run/attempt too. Retain every successfully tested master stage independently of API/docs/content diff filtering. Keep the vetted v3 fixture path and source-cutoff rules intact.
|
||||
|
||||
Revise producer/consumer schema together; strict field allowlists, full SHAs, run ID/attempt and release/hash. Select artifacts by exact ID plus expected attempt-specific name and selected-attempt successful jobs. Reject duplicate, stale, partial, mixed, expired, relabeled or changing-run evidence and archive extras. Complete reruns are allowed only with all selected-attempt jobs/artifacts; selective reruns missing an OS fail closed. Old artifacts remain retained; no silent fallback to the old receipt schema. Promotion continues to trust only the original canonical push/master producer, never the dispatch test run.
|
||||
|
||||
### D04 B: one trusted coordinator and durable lease
|
||||
|
||||
Add Toolkit release-coordinator.yml and a bounded coordinator helper. Wake it on successful canonical push/master CI and via workflow_dispatch from a CLI master-only completion job. CLI wake-up requires successful ordinary Linux/Windows jobs for the reported SHA; it must not depend on private coordinated evidence which the coordinator is meant to obtain. Payloads are hints: independently fetch canonical runs/jobs/master heads, validate source repositories, and reject PR/fork/untrusted artifacts. Ignore evidence-dispatch completion as an automatic new-release trigger to prevent loops. A CLI-triggered wake can use an already retained successful Toolkit master stage; it must not wait for unrelated Toolkit changes.
|
||||
|
||||
Activation guard `CLI_RELEASE_AUTOMATION_ENABLED` in Toolkit defaults to inactive when absent or not exactly `true`. Check it before acquiring/changing the lease, writing pointers or dispatching children, including the first post-merge wake. Session A inspects existing value/timestamp and active runs, verifies credentials and isolated lease contention, then explicitly activates the coordinator and requests one reconciliation. The initial PR/bootstrap steps remain Session A operations before activation. This enables the selected release automation; it never enables production course publication. Turning activation off prevents new operations; any active lease/children must still be reconciled without cancelling or concealing an in-flight npm publish.
|
||||
|
||||
Store a small append-only lease state on Toolkit `refs/heads/automation/cli-release-lease`, outside master. State schema: generation/owner coordinator run+attempt, selected pair, source-stage identity, child evidence/public-release run+attempt, state and timestamps. Use a commit parented to the observed lease commit and non-force fast-forward ref update (or create-if-absent); competing siblings cannot both advance the ref. No external database, R2 or KV. Validate real GitHub contention in an isolated temporary fixture ref before activation, then retire that fixture safely. No production course content is involved.
|
||||
|
||||
Only the lease owner may update candidate/evidence pointers or dispatch release children. Coordinator workflow concurrency is non-cancelling, but the persisted lease is authoritative across repositories/restarts. Hold ownership until all child operations are terminal. On timeout/cancel/API ambiguity, leave recoverable ownership; never steal just because a TTL elapsed. Reconciliation checks owner and all recorded children, adopts the same operation when safe, and advances generation only by conditional ref update. Duplicate/out-of-order wake-ups converge; release jobs verify current lease generation immediately before tag/npm mutations. Inspect existing vars, timestamps and all active/waiting runs before takeover; stop on unexplained newer candidates or nonterminal external writers. Session A manual operations obey the same lease after activation.
|
||||
|
||||
Persist a unique operation identity before every dispatch; correlate returned/discovered child runs by canonical workflow, SHA, attempt and unique operation token. Ambiguous dispatch delivery is a stop/reconciliation case, never an unconditional second publish. Recheck master pair before release and refuse stale checkout; don't retarget a running evidence job to newer master. Completion of an obsolete child is retained evidence, not permission to release the new pair. A later trigger reconciles the new pair. The lease holds through public release completion; partial npm success is held for D07 manual completion.
|
||||
|
||||
Use explicit scoped credentials: CLI `TOOLKIT_DISPATCH_TOKEN` permits Actions write on Toolkit; Toolkit `CLI_RELEASE_CONTROL_TOKEN` permits Actions write, Variables write and Contents read on CLI. Toolkit local GITHUB_TOKEN gets Contents write only in the trusted lease job and Actions write for dispatch. Runtime dispatch uses explicit CLI SHA and does not write Toolkit CLI_CANDIDATE_SHA; Session A owns that bootstrap/PR pointer under the lease. GITHUB_TOKEN is not assumed to grant Variables write. Public CLI retains its existing private-receipt read credential; private content never enters public artifacts. Version writer needs CLI Contents write/PR metadata read; verify actual RELEASE_TOKEN capability rather than trusting its name. No secret values in plan/logs. Credentials are activation prerequisites, not a request to paste secrets or broaden existing tokens silently.
|
||||
|
||||
CLI's trusted master release job also needs Toolkit Contents read for the exact lease ref/commit and bounded lease-state JSON. Provision that scope on TOOLKIT_DISPATCH_TOKEN; Actions read covers owner/child run checks. Its code allowlists only that ref, referenced state commit and regular bounded JSON, does not check out Toolkit, and sanitizes errors. Do not expose this credential to candidate PR execution. GitHub Contents permission is repository-wide even though the reader is path-restricted; this access is an explicit activation scope, not a claim that GitHub grants path-scoped tokens. Existing TOOLKIT_READ_TOKEN remains dedicated to the public receipt verifier.
|
||||
|
||||
The runtime path snapshots exact identity, starts fresh evidence, validates its completion, registers sanitized receipt pointers and dispatches CLI release with immutable inputs. No human copies version/run numbers in the normal post-merge path. Operator merges and content promotion remain outside this automation.
|
||||
|
||||
### D06/D07: directory publication and manual partial completion
|
||||
|
||||
Release CLI ci.yml dispatch runs all ordinary checks and strict private verification against the exact requested master SHA; builds all supported binaries from that SHA before publication completion. Validate committed calculated version, tag absence/match and registry state. Set tag to the proven SHA without a version commit. Use one clean checkout and frozen dependencies/build outputs for packing and directory publication, pinned npm 11.12.1, no lifecycle hooks between the two, no tracked mutation and no generated binary files accidentally added to the npm payload. Keep the credential config and the retained tarball outside the package file set.
|
||||
|
||||
Run npm pack, retain the candidate tarball and its SHA-512, smoke that pack, then npm publish the directory with scripts disabled. npm repacks: **these are not guaranteed to be the same bytes before publishing**. Independently download the actual registry package; require version, registry gitHead, source tag and SHA-512 to match expected. Do not require gitHead embedded in tarball package.json: D06 C relies on registry metadata. Record both expected and actual integrity and assert clean/frozen input inventory; mismatches stop completion and remain an incident because published npm versions are immutable.
|
||||
|
||||
Retain a bounded public manifest tying SHA/version, exact evidence/run/attempt, prepack integrity, binary hashes and original build artifact IDs to the run. If npm already contains this version, never invoke publish again—even if a previous job timed out. Verify whether it is identical and report complete, partial or conflicting. For a compatible partial release, the operator/Session A follows the runbook to verify the original successful artifact jobs and actual registry bytes, then uploads only missing GitHub assets without replacement. No rebuild, wildcard artifact merging or automatic recovery workflow. Missing/expired/mismatched evidence stops recovery; a broken published package needs an ordinary new repair PR/version, not overwrite/unpublish.
|
||||
|
||||
## Phase 1: Bind fresh private evidence to exact sources and attempts
|
||||
|
||||
### Changes Required:
|
||||
|
||||
**Files:** Toolkit .github/workflows/ci.yml; packages/api/scripts/tested-release-stage.mjs, public-coordinated-receipt.mjs, verify-coordinated-inputs.mjs; new packages/api/scripts/verify-retained-stage.mjs and tests under scripts/__tests__/. CLI scripts/verify-coordinated-receipt.mjs and .d.mts; tests/coordinated-receipt.test.ts.
|
||||
|
||||
**Intent:** Introduce release-only retained-stage dispatch and strict attempt-bound receipts while retaining the existing PR candidate path. Keep private artifacts inside Toolkit.
|
||||
|
||||
**Contract:** Revised strict schemas, exact artifact names/IDs, selected-attempt jobs, source-stage identity and separate transient-PR/permanent-master eligibility; every producer/consumer changes together. Old receipts cannot satisfy new release verification.
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
|
||||
- Toolkit targeted receipt/stage/input/workflow tests pass using `pnpm --filter @przeprogramowani/api exec vitest run scripts/__tests__/` with an explicit selected file list.
|
||||
- CLI receipt regressions pass: `bun test tests/coordinated-receipt.test.ts`, including PR-head versus release-master eligibility, old SHA vs merge SHA, run/attempt relabeling, mixed jobs, missing Windows, stale download race, expired/duplicate artifacts and private canaries.
|
||||
- Workflow regressions prove dispatch consumes retained bytes without transform/deploy/publish/notification, every consumer uses exact attempt artifacts, and every successful master stage is retainable without deleting predecessors.
|
||||
|
||||
## Phase 2: Publish the committed version from the proven CLI SHA
|
||||
|
||||
### Changes Required:
|
||||
|
||||
**Files:** CLI package.json (automatically prepared version only during implementation), scripts/auto-version.mjs, .github/workflows/ci.yml; new .github/workflows/prepare-version.yml, scripts/prepare-version.mjs, scripts/release-identity.mjs and tests/auto-version.test.ts, tests/release-identity.test.ts, tests/release-workflow.test.ts; existing tests/smoke/package.test.ts. Toolkit new .github/workflows/release-coordinator.yml, packages/api/scripts/release-coordinator.mjs, packages/api/scripts/lib/release-lease.mjs and corresponding scripts/__tests__/ tests.
|
||||
|
||||
**Intent:** Prepare version automatically on the ordinary CLI PR; remove release-time master writing; automate exact-pair evidence/release under the lease; publish the tested checkout directory and verify the actual registry result. Recovery remains a documented manual operation.
|
||||
|
||||
**Contract:** D01 C/D04 B/D05 comment/D06 C/D07 C as specified above; exact committed version and source remain common to npm/binaries/tag, while byte equality is verified after directory publish. Privileged writers run trusted code only; no candidate scripts receive write credentials.
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
|
||||
- CLI unit, type, lint, node/binary builds and package/binary smoke pass: isolated `bun test tests/*.test.ts`, `bun run typecheck`, `bun run lint`, `bun run build`, `bun run build:binary`, `bun test tests/smoke/`.
|
||||
- Directory-pack/mock-registry regressions with pinned npm prove registry gitHead, exact tag/source/version, stable packing inputs and post-publish SHA-512 equality; deliberate mismatch leaves release incomplete and never republishes.
|
||||
- Workflow regressions prove both ordinary OS checks and exact private evidence precede publishing, all checkouts use the tested SHA, and no release job writes a version commit to master.
|
||||
- Coordinator regressions cover either-repository wake-up, duplicate/out-of-order events, lease contention, interrupted ownership, stale pair or pointers, dispatch identity ambiguity and zero second npm publishes.
|
||||
- Version-preparation regressions prove automatic same-PR numbering from a pinned release baseline, repeat no-op, changed base/head invalidation, bot-triggered CI, fork rejection and no candidate execution with write credentials.
|
||||
|
||||
## Phase 3: Validate rollout instructions and prepare reviewed PRs
|
||||
|
||||
### Changes Required:
|
||||
|
||||
**Files:** Toolkit .github/workflows/promote-content.yml, packages/api/scripts/lib/publication-policy.mjs, packages/api/scripts/r2-sync.mjs and tests; Toolkit docs/how-to/release-10xdevs4-cli.md; CLI new docs/how-to/release-cli.md; both foundation lessons and contract registers; canonical repair artifacts.
|
||||
|
||||
**Intent:** Update promotion's artifact selection for exact producer attempt without admitting dispatch authority. Document automatic versioning, CLI-first bootstrap, lease ownership/reconciliation, credential scopes, manual partial completion, actual-package acceptance and content promotion/withdrawal. Run 10x-lesson and 10x-contract for recurring identity and binding interfaces, then 10x-impl-review for approved implementation.
|
||||
|
||||
**Contract:** Human merges; no production policy enablement. Preserve publisher CAS/storage, server auth/clock/KV, v3 cutoff da989a6f7d4963c275a98943e85d225baf426228, approved maintenance overrides and complete skills. Manual verification gates are not marked complete by automation.
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
|
||||
- Toolkit `pnpm ci:local` passes; promotion tests reject dispatch/non-master/wrong-source/attempt/mixed artifact selections and retain exact stage identity.
|
||||
- `10x-impl-review` reports no unresolved critical implementation findings; scoped PR diffs contain no private content/credentials or unrelated files.
|
||||
- Both repair PRs are created with source identities, test evidence and the concrete human merge sequence; `release-handoff.md` explicitly records unpublished status and Session C prerequisite state.
|
||||
- Session A verifies the scoped automation credentials and a synthetic Git-ref contention trial before enabling the coordinator; unavailable credentials or failed atomic update verification stop activation.
|
||||
|
||||
#### Manual Verification:
|
||||
|
||||
- Operator merges the final automatically versioned CLI repair first against exact Toolkit PR evidence, then merges Toolkit after its PR evidence is refreshed for the actual CLI master SHA.
|
||||
|
||||
## Phase 4: Execute the authorized release sequence and stop at concrete gates
|
||||
|
||||
### Changes Required:
|
||||
|
||||
**Files:** release-handoff.md, operational evidence outside Git, publication and actual-package test records.
|
||||
|
||||
**Intent:** After operator merges and authorized activation, observe the automatic chain and independently verify npm download against production. Reinspect all production inputs at the eventual promotion gate rather than reuse the September 13 snapshot as current truth.
|
||||
|
||||
**Contract:** Coordinate before login email for marcin@przeprogramowani.pl; never request pasted tokens/passwords. Explicitly test upstream Phase 4: highest available authorized fresh course, stable v3 binding, zero writes from preview, no binding on failed preflight. Check complete EN/PL skill folders, login/refresh, sync/local edits and v4 identity after approved publication. Unavailable v4 is a publication gate, never a reason to weaken authorization. Module 1 remains gated by real server time/KV at 2026-09-14T06:00:00Z (08:00 Warsaw); no manufactured clocks/entitlements.
|
||||
|
||||
### Success Criteria:
|
||||
|
||||
#### Automated Verification:
|
||||
|
||||
- New private Linux/Windows evidence and public release succeed for the exact human-merged pair; record all source, run, attempt, original stage, tag, tarball integrity and binary identities.
|
||||
- Actual downloaded npm package passes anonymous installation/help/version checks; following approved login, it verifies account status, highest available authorized fresh selection, stable v3 binding, no preview writes, no binding on failed preflight, EN/PL full skills, sync/refresh/local-edit preservation and exact v4 release identity when published.
|
||||
- Readiness handoff records actual production pointer/lock/Worker/KV state, privately verified v3 backup, retained stage validation, exact promotion and withdrawal/rollback inputs, and remaining human/time gates without asserting unavailable v4 readiness.
|
||||
|
||||
#### Manual Verification:
|
||||
|
||||
- Operator approves the coordinated real-email login flow when needed.
|
||||
- Operator separately approves content policy/environment configuration and the exact validated promotion; before first publication rollback is withdrawal from the newly active pointer, preserving all objects.
|
||||
- After 2026-09-14T06:00:00Z, real production checks confirm module 1 unlock without overrides; until then only authentic locked behavior is claimed.
|
||||
|
||||
## Merge and execution order
|
||||
|
||||
1. Operator reviews this plan and explicitly authorizes implementation. No export or review verdict substitutes for approval.
|
||||
2. Implement/test both isolated branches. Automatically calculate the CLI version and commit it to that repair PR. Session A alone coordinates the exact final PR pair; run Toolkit PR CI and CLI ordinary/receipt checks.
|
||||
3. Operator merges CLI first. The resulting SHA has no valid post-merge evidence yet; no npm release is permitted. The new coordinator must expose pending/missing Toolkit prerequisite rather than use pre-squash proof.
|
||||
4. Session A rechecks candidates/runs and refreshes Toolkit PR proof against actual CLI master. Operator merges Toolkit. Its canonical push/master retains a verified stage for the new Toolkit SHA. Configure scoped credentials and prove lease contention before activation; do not mutate production content.
|
||||
5. Toolkit success or CLI wake causes coordinator reconciliation: acquire lease, pin actual master pair/stage, fresh release-only evidence, validate both OS/receipt, register exact pointer and dispatch CLI release. On an existing version, verify and stop or enter manual completion; never publish again.
|
||||
6. Independently download npm package, record registry version/gitHead/integrity/source pair and perform anonymous then approved-login/v3 acceptance. Package failure does not become a production-ready claim.
|
||||
7. Prepare separately approved content-production policy/environment and exact push/master promotion with rollback. Reinspect R2 pointer/lock, Worker revision, KV and fresh v3 backup. First-publication rollback is withdrawal against the newly active pointer, preserving objects/v3. A policy/source change requires its own valid source-stage/evidence; no transplanted provenance. Test v4 availability/identity and real locked/unlocked behavior after approval/time gate.
|
||||
|
||||
## Testing strategy and operational limits
|
||||
|
||||
New automation is verified with synthetic Git lifecycles, loopback npm registry and mocked GitHub state transitions, followed by a bounded real GitHub fixture-ref contention test only after implementation approval. Existing full CLI and Toolkit CI remain required. Include malicious PR file/script canaries, dispatch spoofing, two simultaneous writers, both merge orders, loss of dispatch response, stale master advancement, npm success with GitHub failure and safe restart. Do not duplicate product behavior tests; rerun the existing selection/binding/full-skill regressions and verify the actual released package.
|
||||
|
||||
D01 C retains the companion-PR prerequisite; it does not promise standalone CLI PR support. Version automation adds no extra human version approval but must stop on an invalidated/stale baseline. D04 B requires scoped credentials and durable state; its Git-ref atomicity is an activation test, not an assumed production result. Budget one complete retained-byte E2E matrix per post-merge pair, plus existing pre-merge proof; no repeated translation for evidence dispatch. Coordinator polling is bounded and records unresolved children on timeout; no additional SaaS/database. D06 C accepts post-publication mismatch risk; D07 C trades recovery code for verified manual work.
|
||||
|
||||
Progress revision: obsolete entries 2.1 (staged gitHead/automatic recovery), 2.3 (single-tarball publication) and 3.4 (Toolkit-first merge) are retired and their IDs are not reused. New criteria use 2.4–2.7 and 3.5–3.6. All retained step titles remain unchanged and all implementation boxes remain unchecked. This is operator-requested re-planning, not completed implementation.
|
||||
|
||||
## References
|
||||
|
||||
- [Applied decisions](decisions.md), [original export](decisions-record.md), [decision details](decisions-details.md).
|
||||
- [Research](research.md), [decision-alignment research](decision-alignment-research.md), [brief](plan-brief.md), [handoff](release-handoff.md).
|
||||
- [Upstream access plan](../10xdevs4-cli-access/plan.md), especially Phases 4 and 7.
|
||||
- Toolkit docs/how-to/release-10xdevs4-cli.md and publication-policy/release-publisher contracts.
|
||||
|
||||
## Progress
|
||||
|
||||
### Phase 1: Bind fresh private evidence to exact sources and attempts
|
||||
|
||||
#### Automated
|
||||
|
||||
- [x] 1.1 Toolkit targeted receipt/stage/input/workflow tests pass using `pnpm --filter @przeprogramowani/api exec vitest run scripts/__tests__/` with an explicit selected file list.
|
||||
- [x] 1.2 CLI receipt regressions pass: `bun test tests/coordinated-receipt.test.ts`, including PR-head versus release-master eligibility, old SHA vs merge SHA, run/attempt relabeling, mixed jobs, missing Windows, stale download race, expired/duplicate artifacts and private canaries.
|
||||
- [x] 1.3 Workflow regressions prove dispatch consumes retained bytes without transform/deploy/publish/notification, every consumer uses exact attempt artifacts, and every successful master stage is retainable without deleting predecessors.
|
||||
|
||||
### Phase 2: Publish the committed version from the proven CLI SHA
|
||||
|
||||
#### Automated
|
||||
|
||||
- [ ] 2.2 CLI unit, type, lint, node/binary builds and package/binary smoke pass: isolated `bun test tests/*.test.ts`, `bun run typecheck`, `bun run lint`, `bun run build`, `bun run build:binary`, `bun test tests/smoke/`.
|
||||
- [ ] 2.4 Directory-pack/mock-registry regressions with pinned npm prove registry gitHead, exact tag/source/version, stable packing inputs and post-publish SHA-512 equality; deliberate mismatch leaves release incomplete and never republishes.
|
||||
- [ ] 2.5 Workflow regressions prove both ordinary OS checks and exact private evidence precede publishing, all checkouts use the tested SHA, and no release job writes a version commit to master.
|
||||
- [ ] 2.6 Coordinator regressions cover either-repository wake-up, duplicate/out-of-order events, lease contention, interrupted ownership, stale pair or pointers, dispatch identity ambiguity and zero second npm publishes.
|
||||
- [ ] 2.7 Version-preparation regressions prove automatic same-PR numbering from a pinned release baseline, repeat no-op, changed base/head invalidation, bot-triggered CI, fork rejection and no candidate execution with write credentials.
|
||||
|
||||
### Phase 3: Validate rollout instructions and prepare reviewed PRs
|
||||
|
||||
#### Automated
|
||||
|
||||
- [ ] 3.1 Toolkit `pnpm ci:local` passes; promotion tests reject dispatch/non-master/wrong-source/attempt/mixed artifact selections and retain exact stage identity.
|
||||
- [ ] 3.2 `10x-impl-review` reports no unresolved critical implementation findings; scoped PR diffs contain no private content/credentials or unrelated files.
|
||||
- [ ] 3.3 Both repair PRs are created with source identities, test evidence and the concrete human merge sequence; `release-handoff.md` explicitly records unpublished status and Session C prerequisite state.
|
||||
- [ ] 3.6 Session A verifies the scoped automation credentials and a synthetic Git-ref contention trial before enabling the coordinator; unavailable credentials or failed atomic update verification stop activation.
|
||||
|
||||
#### Manual
|
||||
|
||||
- [ ] 3.5 Operator merges the final automatically versioned CLI repair first against exact Toolkit PR evidence, then merges Toolkit after its PR evidence is refreshed for the actual CLI master SHA.
|
||||
|
||||
### Phase 4: Execute the authorized release sequence and stop at concrete gates
|
||||
|
||||
#### Automated
|
||||
|
||||
- [ ] 4.1 New private Linux/Windows evidence and public release succeed for the exact human-merged pair; record all source, run, attempt, original stage, tag, tarball integrity and binary identities.
|
||||
- [ ] 4.2 Actual downloaded npm package passes anonymous installation/help/version checks; following approved login, it verifies account status, highest available authorized fresh selection, stable v3 binding, no preview writes, no binding on failed preflight, EN/PL full skills, sync/refresh/local-edit preservation and exact v4 release identity when published.
|
||||
- [ ] 4.3 Readiness handoff records actual production pointer/lock/Worker/KV state, privately verified v3 backup, retained stage validation, exact promotion and withdrawal/rollback inputs, and remaining human/time gates without asserting unavailable v4 readiness.
|
||||
|
||||
#### Manual
|
||||
|
||||
- [ ] 4.4 Operator approves the coordinated real-email login flow when needed.
|
||||
- [ ] 4.5 Operator separately approves content policy/environment configuration and the exact validated promotion; before first publication rollback is withdrawal from the newly active pointer, preserving all objects.
|
||||
- [ ] 4.6 After 2026-09-14T06:00:00Z, real production checks confirm module 1 unlock without overrides; until then only authentic locked behavior is claimed.
|
||||
@@ -0,0 +1,93 @@
|
||||
# Session A release handoff
|
||||
|
||||
**Status: operator approved the reviewed plan and invoked 10x-goal-implement on 2026-09-13. Phase 1 implementation passed targeted, deliberate-break and full repository gates; Phase 2 remains pending. No repaired package has been published by this session. Last production inspection showed unpublished v4. Session C stage-1 prerequisite is NOT satisfied.**
|
||||
|
||||
Canonical folder: `/Users/admin/code/10x-cli-release-repair/context/changes/cli-release-repair`. Read [plan brief](plan-brief.md), [plan](plan.md) and [research](research.md). This file is the release coordination handoff; other sessions must not change shared candidate variables/run pointers.
|
||||
|
||||
## Current authorization — implement through the human merge gate
|
||||
|
||||
The operator exported record SHA-256 `792e4b3ba300d81e5db00f5e12dbc899eb585d6ba27639fc4411ffc871d1ddc6`. Its actual bytes and applied decisions.md were read. Coordinator reports dry-run/apply saved 8, conflict 0, rejected 0 using `/Users/admin/code/10x-decision-room/.tmp/changes/cli-release-repair-344449df/config.json`. Do not regenerate the export, edit owner fields or apply it again. Export/review is not implementation authorization.
|
||||
|
||||
Choices: D01 C, D02 A, D03 A, D04 B, D05 A + automation comment, D06 C, D07 C, D08 A. Read [corrected plan](plan.md), [brief](plan-brief.md) and [alignment research](decision-alignment-research.md). Independent [plan review](reviews/plan-review.md): **SOUND**, all five dimensions PASS, no unresolved findings after two targeted corrections. Plan SHA-256: `f46f2c7789a2a1844af076198ceb3c9d2dae877a7a465cdd4ffce5ef4168fb59`. Operator subsequently instructed “Działaj zgodnie z 10xWorkflow/10x-goal-implement”, approving this exact plan. Session A is implementing the authorized scope; next human actions are the concrete CLI-first and Toolkit-second merges after tested PRs exist. No further numerical version approval is needed.
|
||||
|
||||
The corrected design automatically prepares the version in the same ordinary code PR; reverses initial merge order to CLI then Toolkit; automates post-merge evidence and publication with a durable lease; publishes a directory and verifies its actual npm integrity; retains manual recovery for partial publication. D01 C still requires a real Toolkit companion PR for pre-merge checks of future CLI-only work. Scoped credentials, bounded Toolkit lease reads and isolated lease contention verification are activation prerequisites described precisely in the plan. The coordinator starts inactive until Session A validates and activates it.
|
||||
|
||||
Code changes, regression gates and PR preparation are now authorized. Human merges, coordinated login and separate production-promotion approval remain required; no PR is ready yet.
|
||||
|
||||
## Exact identities, observed 2026-09-13
|
||||
|
||||
| Item | Identity / result |
|
||||
|---|---|
|
||||
| CLI fresh master / repair base | `b0c789af70f30255fb05149ad86f8534f96c2cb6` (#38) |
|
||||
| Toolkit fresh master / companion base | `39925ab6155c9c17fbdabd69d160b5f4fe928c4e` (#31) |
|
||||
| Branches | `fix/cli-v4-release-evidence` in both isolated worktrees |
|
||||
| Toolkit companion | `/private/tmp/10x-toolkit-release-repair` |
|
||||
| Published npm latest | `@przeprogramowani/10x-cli@1.20.0` |
|
||||
| Published gitHead | `f89f19506cab8c9bbeb112242e4485fce4f1b77b` |
|
||||
| Published npm integrity | `sha512-fi38hdT9bOjZRMel7NTb11Kp/3T2tsKahMOYUW5zeUUgP8GpfXxksHihyyTvLms0ElWGob/6724PfEOCpBF2Og==` |
|
||||
| Computed example next version | Existing bump rules give `1.21.0` from v1.20.0/#38; no manual number approval. Recalculate from the pinned published baseline before preparing the actual PR; not yet committed/built/published. |
|
||||
| Failed release run | [CLI 34745146982](https://github.com/przeprogramowani/10x-cli/actions/runs/34745146982), attempt 1, ordinary Linux/Windows PASS, exact evidence FAIL, publishing skipped |
|
||||
| Retained successful private run | [Toolkit 34743867441](https://github.com/przeprogramowani/10x-toolkit/actions/runs/34743867441), attempt 1, push/master, both OS/receipt/stage/deploy PASS |
|
||||
| CLI actually proven by retained run | `c139ac5d89bc935be67174cc031e96f62661036d`, not the merged CLI |
|
||||
| Public receipt artifact | ID `10313509151`, `coordinated-receipt`, unexpired at inspection |
|
||||
| Retained stage artifact | ID `10313382854`, `v4-release`, 13 files, unexpired at inspection |
|
||||
| Retained release ID | `r-88e2e5394c21e8a8509220a0f78700b046d7682e9bdafe5b15a7e10dc9514228` |
|
||||
| Retained manifest SHA-256 | `d58337f2004fd3eba0f5ca268f57729c626aaf6063f64ed9dd94f20280379c30` |
|
||||
| Stage source / validation | Toolkit `39925ab...`; manifest hash and all 12 listed object sizes/hashes verified by `readStage` |
|
||||
| Current Worker version | `4a635328-e005-4d8b-91b6-a48000dd9c48`, 100% traffic |
|
||||
| Current Worker deployment | `562dab9c-5a3c-4f37-95f1-f16becfa819d`, 2026-09-13T07:07:54.304Z; annotation ties it to Toolkit `39925ab` / run `34743867441` |
|
||||
| Actual production v4 pointer / lock | Both absent; full `10xdevs4/` prefix contains zero objects at 2026-09-13T08:22:05Z |
|
||||
| Module 1 KV override | `stateOverride:10xdevs4:1` GET returned 404; none written |
|
||||
| Module 1 schedule | `2026-09-14T06:00:00Z` = 08:00 Europe/Warsaw; server/KV gates preserved |
|
||||
| Production publication policy | `productionEnabled:false`, `securedWorkerRevision:null` |
|
||||
| Promotion environment | `content-production` GET 404; repository environment list empty |
|
||||
|
||||
At the earlier research inspection, repository variables were: Toolkit CLI_CANDIDATE_SHA=`c139ac5...`; CLI TOOLKIT_CANDIDATE_SHA=`39925ab...`; CLI TOOLKIT_COORDINATED_RUN_ID=`34743867441`. Full values/timestamps and fixture identities are in research. No active, queued or environment-waiting runs were found at that inspection. This planning revision did not refresh run/variable or production state and did not mutate it. Reinspect immediately before any future operation; this snapshot is not a lock.
|
||||
|
||||
## Evidence already completed
|
||||
|
||||
- Reproduced exact current receipt accepting pre-squash CLI and rejecting actual merged CLI.
|
||||
- Reproduced identical synthetic OS receipts being reissued under attempt 1 and 2 by the existing producer. Implementation approval is now recorded; the regression was reproduced RED and Phase 1 repair is in progress. No repaired green result is claimed yet.
|
||||
- 86 focused receipt/selection/binding/safety tests PASS.
|
||||
- Full current CLI unit/integration baseline: **647 PASS, 0 FAIL**, 35 files, with isolated XDG config. Initial sandbox run failed on localhost/auth-lock restrictions; successful rerun used the same source outside those restrictions.
|
||||
- CLI typecheck PASS; lint 0 errors / 3 existing warnings. Node and standalone binary builds PASS; these are local unpublished builds reporting source version 1.20.0.
|
||||
- Smoke verified: initial 9 PASS / 1 npm-pack environment failure, then all 4 package smoke tests PASS with private npm cache/outside sandbox. Combined binary/course/package smoke coverage passes; this is baseline, not new-release evidence.
|
||||
- Planning revision: synthetic directory publication using real npm 11.12.1 against a loopback mock registry passed (same bytes/SHA-512, registry gitHead equal fixture commit, clean tracked source). This is feasibility evidence, not the actual CLI npm release. Details in decision-alignment-research.md.
|
||||
- Direct R2 inspection, retained GitHub-stage validation and deployed Worker metadata checked using pinned Wrangler 4.80.0 and existing storage client. No R2 mutations.
|
||||
- New private v3 backup: **57 objects, 35,776,634 bytes**, inventory SHA-256 `f56176d8be1765862442868fd32309f73fc3e229d4fa25e0baf3db631745cd5f`. All local bytes verified; second remote full compare reports unchanged. Client printed reconnect diagnostics but both commands completed and independent hashes matched.
|
||||
- Current v3 differs from historical E2E fixture: same 57 keys, 33 changed object hashes. Preserve the actual current baseline; do not restore or republish the old fixture. This difference pre-exists this session; its origin was not established here.
|
||||
|
||||
Private raw artifacts, backups and test logs: `/private/tmp/10x-release-repair-evidence-20260913/`. They are outside both repositories. No credentials or paid content belong in public PRs/logs.
|
||||
|
||||
## Selected design and execution order after authorization
|
||||
|
||||
All eight exported choices, including the D05 comment, supersede the earlier plan's recommendations. No repair PRs exist yet; implementation has started under the approved plan. Minimum version automation is deterministic preparation on the same code PR, reusing the existing bump rules; it adds no separate version PR or approval. Literal zero additional logic cannot repair the current post-evidence writer, but no new numbering service is proposed.
|
||||
|
||||
After plan approval: implement/test the two repair PRs; automatically prepare final CLI version and prove exact Tpr/Cpr through the Toolkit PR path; operator merges CLI first; refresh Toolkit PR evidence against actual CLI master Cm; operator merges Toolkit; canonical Tm push/master retains its stage. Provision the scoped coordinator credentials and validate lease contention at activation. The coordinator reacts to either repository, serializes the operation, produces release-only Tm/Cm evidence, registers its receipt and starts public CLI release for Cm. Actual npm download must prove registry gitHead/version/tag and post-publication integrity. No old fixed-name successful run is rerun or discarded as a shortcut.
|
||||
|
||||
Directory publication can diverge from the earlier pack; D06 C accepts detection after the immutable publish. A mismatch is an incomplete release. D07 C blocks a second npm publish and uses the original verified artifacts plus manual completion of missing GitHub assets. A real login approval and production content-promotion approval remain separate gates.
|
||||
|
||||
## Content promotion and rollback preparation
|
||||
|
||||
This is a separate gate; no policy/environment/pointer change is authorized by the repair-plan choices. The observed current identity is exact JSON `null`. Existing retained release/hash above is validated storage input, but its CLI evidence is pre-squash and must not be represented as the final repair pair.
|
||||
|
||||
Before presenting the final promotion for approval:
|
||||
|
||||
1. Finish new receipt/stage workflow repair and human merges. Select a successful canonical **push/master** source run, its explicit attempt and exact retained artifact ID; revalidate all stage bytes, schedule, curriculum/full skills and source ancestry. A dispatch run cannot substitute as content producer.
|
||||
2. Verify secured Worker source/traffic and access guards; present a narrowly scoped policy PR setting productionEnabled and the exact secured source floor. Prepare `content-production` master restriction, required human reviewer and least-privileged credential setup without printing/requiring pasted secret values. Operator approves the concrete production-enablement configuration and performs its merge.
|
||||
3. Reinspect active writers, production pointer/lock, KV state and v3 baseline. Stop on an unexplained newer pointer/candidate or nonterminal lock holder. Refresh stage/evidence if the selected Toolkit SHA changes.
|
||||
4. Submit exact promote inputs: canonical source run/attempt, selected release ID, manifest hash, `expected_current` equal to the observed pointer (`null` only while still absent). Canonical `promote-content.yml` uploads/verifies immutable objects then CAS-switches current. No direct R2 PUT, clock change or entitlement workaround.
|
||||
5. First-publication rollback is **withdraw**, with expected_current equal to the newly active pointer. It creates a tombstone, preserves all objects and v3, and blocks historical v4 reads. Rollback to a different release is possible only after verifying an actual retained previous release. Do not roll back to the old unguarded Worker or delete the pointer/lock blindly.
|
||||
6. Verify production manifest/hash, complete EN/PL object inventory and unchanged v3 inventory; then verify the actual downloaded CLI against production. Before 06:00 UTC on 14 September, locked module behavior is expected; unlocked acceptance remains pending until real server time reaches the boundary.
|
||||
|
||||
The exact final promotion command cannot yet be issued honestly: the repair/policy commits, final producer attempt, environment and fresh paired evidence do not exist. This handoff records the current validated inputs and the exact remaining prerequisites rather than claiming promotion readiness.
|
||||
|
||||
## Actual downloaded-package acceptance still required
|
||||
|
||||
After publishing the new version, independently fetch registry metadata and tarball into a new isolated install; verify SHA-512 and gitHead against release evidence, record package version, tag SHA and source pair. Do not reuse local dist or the historical candidate tarball.
|
||||
|
||||
Coordinate a real login link for `marcin@przeprogramowani.pl` before sending it; operator approves in browser, no pasted tokens/passwords. Verify auth/refresh, default highest available authorized selection, explicit course list/get, stable v3 projects (including released-1.20.0 legacy manifests), and no fallback from bound unavailable courses. Snapshot project/preferences before list/print/dry-run and failed preflight; verify zero changes/no new binding. Verify EN/PL, complete skill support trees, sync, missing-file repair and local-edit preservation. When v4 is published, match returned/installed release ID and manifest hash exactly. Keep locked/unlocked checks tied to actual server time/KV state.
|
||||
|
||||
Historical real-email results in `/Users/admin/code/10x-cli-v4-delivery/context/changes/10xdevs4-cli-access/package-manual-results-2026-09-13.md` remain useful regressions, not a substitute for the actual new npm package.
|
||||
|
||||
**One-line handoff:** Session A release authority and remaining gates: `/Users/admin/code/10x-cli-release-repair/context/changes/cli-release-repair/release-handoff.md`; Session C stage 1 is not satisfied.
|
||||
@@ -0,0 +1,91 @@
|
||||
---
|
||||
date: 2026-09-13T10:27:00+02:00
|
||||
researcher: Session A
|
||||
git_commit: b0c789af70f30255fb05149ad86f8534f96c2cb6
|
||||
branch: fix/cli-v4-release-evidence
|
||||
repository: 10x-cli
|
||||
topic: Post-squash exact-pair evidence and release handoff
|
||||
tags: [research, release, ci, provenance]
|
||||
status: complete
|
||||
last_updated: 2026-09-13
|
||||
last_updated_by: Session A
|
||||
---
|
||||
|
||||
# Research: post-merge release repair
|
||||
|
||||
## Research question
|
||||
|
||||
Why did CLI #38 fail publication, how can fresh evidence retain strict commit/run/platform identity, and what remains before actual v4 production availability?
|
||||
|
||||
## Summary
|
||||
|
||||
The public gate correctly rejects pre-squash evidence. A second gap exists after that gate: automatic versioning publishes a new commit that the gate never tested. Toolkit cannot safely refresh successful CI evidence using existing fixed artifact names. Platform receipts lack run/attempt identity, permitting their relabeling by a later receipt producer. These are release-process defects; unavailable v4 is a separate publication gate.
|
||||
|
||||
## Current observed state
|
||||
|
||||
Both repositories were fetched; CLI repair HEAD equals origin/master `b0c789af70f30255fb05149ad86f8534f96c2cb6`, Toolkit companion equals origin/master `39925ab6155c9c17fbdabd69d160b5f4fe928c4e`. No applicable filesystem AGENTS.md was found in ancestors or either worktree; operator-supplied instructions apply. Original Toolkit worktree has unrelated tracked/untracked edits and remains untouched.
|
||||
|
||||
| Setting | Value | Last remote update |
|
||||
|---|---|---|
|
||||
| Toolkit CLI_CANDIDATE_SHA | c139ac5d89bc935be67174cc031e96f62661036d | 2026-09-13T06:23:51Z |
|
||||
| CLI TOOLKIT_CANDIDATE_SHA | 39925ab6155c9c17fbdabd69d160b5f4fe928c4e | 2026-09-13T06:54:55Z |
|
||||
| CLI TOOLKIT_COORDINATED_RUN_ID | 34743867441 | 2026-09-13T07:08:50Z |
|
||||
| Toolkit V3_E2E_FIXTURE_RUN_ID | 34741021103 | 2026-09-13T05:45:05Z |
|
||||
| Toolkit V3_E2E_FIXTURE_HEAD_SHA | 5141b5bd00b238be4e59ef86aa1cfd9c93753d62 | 2026-09-13T05:45:05Z |
|
||||
| Toolkit V3_E2E_INVENTORY_SHA256 | ca8a7993686793b9ddde461ccdfec305b509a7f57e78a59670d6be6f4c30d9e0 | 2026-09-13T05:45:06Z |
|
||||
|
||||
Both repositories had zero in-progress and queued runs at inspection. No variable, workflow, entitlement, clock, publication policy or remote content was changed in research.
|
||||
|
||||
## Detailed findings
|
||||
|
||||
### Correct squash rejection
|
||||
|
||||
[CLI ci.yml](https://github.com/przeprogramowani/10x-cli/blob/b0c789af70f30255fb05149ad86f8534f96c2cb6/.github/workflows/ci.yml#L101) passes the actual PR head or push SHA. `scripts/verify-coordinated-receipt.mjs:39` compares exact identity. Downloaded sanitized receipt from [Toolkit run 34743867441](https://github.com/przeprogramowani/10x-toolkit/actions/runs/34743867441), attempt 1, binds Toolkit master to CLI `c139ac5...`. Executing the current validator accepts that original SHA and rejects merged `b0c789a...`.
|
||||
|
||||
[CLI run 34745146982](https://github.com/przeprogramowani/10x-cli/actions/runs/34745146982) passed check/check-windows, failed the exact-pair job, and skipped version/npm/binaries/GitHub release. Its failure is not an ordinary OS regression.
|
||||
|
||||
### Artifact lifecycle and attempt mixing
|
||||
|
||||
Toolkit `.github/workflows/ci.yml:3` supports only push/PR. Fixed upload names at lines 161, 248, 259, 294 and 433 cover content, OS receipts, diagnostics, public receipt and retained release. Wildcard receipt downloads at lines 284/418 can mix generations if uploads are changed without coordinated consumer changes.
|
||||
|
||||
`packages/api/scripts/tested-release-stage.mjs:12` emits OS receipts without run ID/attempt. `public-coordinated-receipt.mjs:52` assigns the producer's current identity. A local executable reproduction accepted identical synthetic OS receipt bytes as attempt 1 and attempt 2. This is an observed current defect, not a fixed regression. Private reproduction: `/private/tmp/10x-release-repair-evidence-20260913/attempt-relabel-reproduction.json`.
|
||||
|
||||
CLI verifier queries jobs with `filter=latest` at line 77. It verifies final receipt attempt and rechecks run metadata, but must query and validate the selected attempt's jobs explicitly. GitHub's [artifact action](https://github.com/actions/upload-artifact#inputs) documents that overwrite deletes existing artifacts; [rerun documentation](https://docs.github.com/en/actions/how-tos/manage-workflow-runs/re-run-workflows-and-jobs) explains reruns retain the original SHA/ref. Neither is a safe substitute for a new evidence identity.
|
||||
|
||||
### Untested version commit
|
||||
|
||||
CLI `.github/workflows/ci.yml:143–153` commits package.json and tags/pushes a new master SHA after evidence. npm/binary checkouts at lines 180/204 build that tag, not the evidence SHA. `scripts/auto-version.mjs` writes the new version. Existing smoke tests check bump calculation but do not assert evidence SHA = package source SHA = tag SHA.
|
||||
|
||||
### Retained content and promotion boundary
|
||||
|
||||
Downloaded `v4-release` artifact ID `10313382854` has 13 files, manifest plus 12 listed objects. `readStage` verified every listed size/hash and mapping association. Release ID `r-88e2e5394c21e8a8509220a0f78700b046d7682e9bdafe5b15a7e10dc9514228`, manifest SHA-256 `d58337f2004fd3eba0f5ca268f57729c626aaf6063f64ed9dd94f20280379c30`, source Toolkit `39925ab...`. It remains a retained GitHub artifact tested with the old CLI commit.
|
||||
|
||||
Toolkit `lib/publication-policy.mjs:24` only accepts successful canonical push/master CI; line 35 binds stage provenance to its SHA. This trust boundary stays push-only under the operator's selected design. Evidence dispatch must not silently become a content producer. A dispatch using retained bytes needs a source run/attempt/artifact identity distinct from its new test run/attempt.
|
||||
|
||||
`upload-content` currently depends on content-change detection (`ci.yml:394`); API/docs-only master commits may have no retained stage; the current path filter already includes CI changes. The repaired producer must retain every successfully tested master stage needed for exact-source rechecks. Its historical job name does not imply an R2 write.
|
||||
|
||||
### Actual production inspection
|
||||
|
||||
At `2026-09-13T08:22:05Z`, direct R2 binding reads and complete prefix listing returned current=null, publisher-lock=null, and zero objects under `10xdevs4/`. This confirms unpublished v4 independently of discovery. `available:false` alone would also be consistent with published but time-locked lessons (`routes/me.ts:66–90`).
|
||||
|
||||
Current Worker deployment `562dab9c-5a3c-4f37-95f1-f16becfa819d` routes 100% to version `4a635328-e005-4d8b-91b6-a48000dd9c48`; deployment annotation identifies `39925ab` and run `34743867441`, deployed `2026-09-13T07:07:54.304Z`. That run's deploy-worker succeeded. This is the observed secured source floor; new live guard/account verification still belongs to readiness.
|
||||
|
||||
KV read for `stateOverride:10xdevs4:1` returned HTTP 404. The configured module date is `2026-09-14T06:00:00Z` = 08:00 Europe/Warsaw. `module-state.ts:8–10` gives explicit KV override precedence over server time. No override was written.
|
||||
|
||||
Publication policy remains false/null. GitHub GET environment `content-production` returned 404. Complete v3 snapshot captured outside Git: `/private/tmp/10x-release-repair-evidence-20260913/v3-baseline` (57 objects). Network reconnect diagnostics occurred but the inventory command completed successfully; independent hash verification and comparison are recorded in the handoff. Refresh this baseline at the actual promotion boundary if intervening writers invalidate it.
|
||||
|
||||
## Package baseline
|
||||
|
||||
Registry latest remains `@przeprogramowani/10x-cli@1.20.0`, gitHead `f89f19506cab8c9bbeb112242e4485fce4f1b77b`, integrity `sha512-fi38hdT9bOjZRMel7NTb11Kp/3T2tsKahMOYUW5zeUUgP8GpfXxksHihyyTvLms0ElWGob/6724PfEOCpBF2Og==`. Prior real-email/package tests are historical: manual results at `/Users/admin/code/10x-cli-v4-delivery/context/changes/10xdevs4-cli-access/package-manual-results-2026-09-13.md`. They do not validate a newly published repaired package.
|
||||
|
||||
86 focused CLI tests passed. Full baseline initially encountered sandbox port/auth-lock restrictions and was rerun with isolated XDG configuration; final outcome belongs in handoff. No production login email has been sent in this session.
|
||||
|
||||
## Architecture and historical context
|
||||
|
||||
Permanent content pins must remain reachable from fetched origin/master; PR candidates and equivalent trees never substitute for retained commit identity. V3 cutoff stays `da989a6f7d4963c275a98943e85d225baf426228`; reviewed maintenance exceptions stay `5141b5bd00b238be4e59ef86aa1cfd9c93753d62`. Full skill packaging, auth host/signature restrictions and server-side module gating stay unchanged.
|
||||
|
||||
Canonical upstream plan Phase 4 requires highest available authorized fresh-project edition, stable existing v3 binding, no preview writes, and no binding on failed preflight. Phase 7 remains incomplete. Historical publisher trial verified conditional behavior in a separate test bucket, not production.
|
||||
|
||||
## Decisions and remaining uncertainty
|
||||
|
||||
Operator chose version in the repair PR and fresh evidence over exact retained bytes, with promotion still trusting the original push/master producer. Concrete plan review/approval remains required before implementation. Environment reviewers/credential configuration and exact eventual content promotion remain separate human gates; no real login, entitlement change or production unlock has been simulated.
|
||||
@@ -0,0 +1,63 @@
|
||||
<!-- PLAN-REVIEW-REPORT -->
|
||||
# Plan Review: exact-commit CLI release after operator decisions
|
||||
|
||||
- **Plan**: ../plan.md
|
||||
- **Plan SHA-256**: `f46f2c7789a2a1844af076198ceb3c9d2dae877a7a465cdd4ffce5ef4168fb59`
|
||||
- **Decision Record SHA-256**: `792e4b3ba300d81e5db00f5e12dbc899eb585d6ba27639fc4411ffc871d1ddc6`
|
||||
- **Mode**: Deep, targeted independent review plus parent verification
|
||||
- **Date**: 2026-09-13
|
||||
- **Reviewer**: /root/revised_plan_review; parent Session A
|
||||
- **Verdict**: SOUND after targeted corrections
|
||||
- **Findings**: 0 unresolved critical, 0 unresolved warnings; 2 warnings fixed and 2 contract clarifications included
|
||||
- **Authorization**: Planning review only. No implementation or production approval.
|
||||
|
||||
## Verdicts
|
||||
|
||||
| Dimension | Verdict |
|
||||
|---|---|
|
||||
| End-State Alignment | PASS |
|
||||
| Lean Execution | PASS |
|
||||
| Architectural Fitness | PASS |
|
||||
| Blind Spots | PASS |
|
||||
| Plan Completeness | PASS |
|
||||
|
||||
## Grounding
|
||||
|
||||
Grounding: 6/6 existing paths verified; producer/consumer symbols traced; brief and plan agree. Relevant symbols include validateProducerRun, verifyCandidateCheckout, testedStageReceipt and validateGreenBuild/validateGreenStage. Existing paths include CLI ci.yml, auto-version.mjs, verifier and declaration, smoke package tests and Toolkit publication runbook. Newly proposed helpers/workflows are explicitly labeled new.
|
||||
|
||||
Progress: exactly one bottom Progress block, four matching phase titles, 19 success criteria matched one-for-one, no body checkboxes and no completed implementation rows. Retained step titles remain unchanged; retired 2.1/2.3/3.4 IDs were not reused. Parent script independently confirmed both decision-file hashes unchanged and all eight owner-field sets equal the actual export. Source validator: `OK: 8 decyzji w obu plikach.`
|
||||
|
||||
Blast-radius sweep includes promotion's fixed artifact selection, r2-sync/publication-policy consumers and the existing prepare-v3-fixture-artifact importer of verifyV3Fixture; preserve that signature. No contract register currently exists. No new product/auth/EDU dependency was found.
|
||||
|
||||
## Findings
|
||||
|
||||
### F1 — Initial automation activation was not guarded
|
||||
|
||||
- **Severity**: WARNING
|
||||
- **Impact**: LOW — narrow activation contract correction
|
||||
- **Dimension**: Blind Spots
|
||||
- **Location**: D04 coordinator and merge sequence
|
||||
- **Detail**: The first Toolkit master success could start coordination before credentials and lease contention had been verified.
|
||||
- **Fix**: Default-inactive CLI_RELEASE_AUTOMATION_ENABLED guard checked before coordination writes/dispatches; Session A activates only after prerequisite checks and explicitly requests reconciliation. Active children remain recoverable if new starts are disabled.
|
||||
- **Decision**: FIXED in plan; independently rechecked. This is a planning correction, not an activation performed by this session.
|
||||
|
||||
### F2 — Private lease reader lacked an explicit credential scope
|
||||
|
||||
- **Severity**: WARNING
|
||||
- **Impact**: LOW — specify the already-required read contract
|
||||
- **Dimension**: Plan Completeness
|
||||
- **Location**: D04 credential matrix
|
||||
- **Detail**: The trusted CLI release job must validate a Toolkit Git-ref lease, but Actions dispatch permission alone does not grant Contents read.
|
||||
- **Fix**: Specify Toolkit Contents read on TOOLKIT_DISPATCH_TOKEN, bounded allowlisted lease reads from trusted master code, no candidate credential access and explicit repository-wide scope limitation.
|
||||
- **Decision**: FIXED in plan; independently rechecked. Token provisioning/capability verification remains an implementation-activation prerequisite.
|
||||
|
||||
## Additional contract clarifications verified
|
||||
|
||||
- Squash changes history: numbering provenance binds a trusted preparation workflow/run/attempt, exact artifact, input/resulting PR head, base and published baseline. Bootstrap recomputes from trusted newly merged helper against original PR objects. This record does not replace fresh post-squash commit evidence.
|
||||
- A successful published-baseline advancement also reconciles open PR preparation without requiring another head/base commit; the plan requires that regression.
|
||||
|
||||
## Selection fidelity and feasibility
|
||||
|
||||
D01 C's CLI-first bootstrap works for this paired repair using the existing exact Toolkit PR path. The plan explicitly retains the real Toolkit companion-PR prerequisite for future CLI-only pre-merge tests; no silent dispatch PR mode or evidence weakening. D05 honors the operator's comment with automatic same-code-PR numbering and ordinary human code merge, not separate version acceptance. D04 B includes two-source wake-up, durable ownership and permission/activation checks. D06 C acknowledges repacking and post-publish integrity detection; D07 C keeps verified manual partial completion with no second npm publish. D08 and production authorization/time gates remain intact.
|
||||
|
||||
No further operator design choice is necessary to finish this planning revision. Approval of this exact concrete plan remains required before implementation. Live GitHub lease contention, credential capabilities, real CLI directory-pack regressions, actual published package and production acceptance are still future evidence, not established by this review.
|
||||
@@ -1,6 +1,11 @@
|
||||
export interface CoordinatedIdentity { runId: string; toolkitSha: string; cliSha: string }
|
||||
export interface CoordinatedIdentity { runId: string; toolkitSha: string; cliSha: string; mode?: "pr" | "release"; runAttempt?: number; artifactId?: string; leaseGeneration?: string }
|
||||
export function validateExpectedIdentity(identity: CoordinatedIdentity): void;
|
||||
export function validateProducerRun(run: unknown, identity: CoordinatedIdentity): number;
|
||||
export function validatePlatformJobs(result: unknown): void;
|
||||
export function validatePlatformJobs(result: unknown, identity: { runId: string; toolkitSha: string }, runAttempt: number, required?: string[]): void;
|
||||
export function validatePublicReceipt<T>(receipt: T, identity: CoordinatedIdentity, runAttempt: number): T;
|
||||
export function validateReceiptArtifact(result: unknown): void;
|
||||
export interface ReceiptArtifact { id: number; size_in_bytes: number; digest: string }
|
||||
export function validateReceiptArtifact(result: unknown, identity: { runId: string; toolkitSha: string; artifactId?: string }, runAttempt: number, base?: string, maximumSize?: number): ReceiptArtifact;
|
||||
export function validateLivePullRequest(pr: unknown, toolkitSha: string): void;
|
||||
export function validateSourceRun(run: unknown, receipt: unknown): void;
|
||||
export function readReceiptArchive(zip: Buffer, artifact: ReceiptArtifact): unknown;
|
||||
export function verifyCoordinatedEvidence(identity: CoordinatedIdentity, dependencies: { get: (path: string, repo?: string) => Promise<any>; download: (artifact: ReceiptArtifact) => Promise<Buffer> }): Promise<unknown>;
|
||||
|
||||
@@ -1,106 +1,185 @@
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { lstatSync, mkdtempSync, readFileSync, readdirSync, rmSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join, resolve } from "node:path";
|
||||
import { createHash } from "node:crypto";
|
||||
import { inflateRawSync } from "node:zlib";
|
||||
import { resolve } from "node:path";
|
||||
import { pathToFileURL } from "node:url";
|
||||
|
||||
const repository = "przeprogramowani/10x-toolkit";
|
||||
const sha = /^[a-f0-9]{40}$/;
|
||||
const keys = ["schemaVersion", "repository", "runId", "runAttempt", "toolkitSha", "cliSha", "platforms", "releaseId", "manifestHash"].sort();
|
||||
const cliRepository = "przeprogramowani/10x-cli";
|
||||
const sha = (value) => typeof value === "string" && /^[a-f0-9]{40}$/.test(value);
|
||||
const id = (value) => typeof value === "string" && /^[1-9][0-9]*$/.test(value);
|
||||
const attempt = (value) => Number.isSafeInteger(value) && value > 0;
|
||||
const keys = ["schemaVersion", "repository", "runId", "runAttempt", "toolkitSha", "cliSha", "platforms", "releaseId", "manifestHash", "sourceRunId", "sourceRunAttempt", "sourceArtifactId", "leaseGeneration"].sort();
|
||||
const platformJobs = ["Coordinated CLI/API (ubuntu-latest)", "Coordinated CLI/API (windows-latest)", "coordinated-receipt"];
|
||||
const evidenceJobs = ["coordinated-inputs", "lint-check", "validate", "prepare-coordinated-content", ...platformJobs];
|
||||
|
||||
export function validateExpectedIdentity(identity) {
|
||||
if (!/^[1-9][0-9]*$/.test(identity.runId ?? "") || !sha.test(identity.toolkitSha ?? "") || !sha.test(identity.cliSha ?? "")) {
|
||||
if (!identity || !id(identity.runId) || !sha(identity.toolkitSha) || !sha(identity.cliSha) ||
|
||||
!["pr", "release"].includes(identity.mode ?? "pr") ||
|
||||
(identity.runAttempt !== undefined && !attempt(identity.runAttempt)) ||
|
||||
(identity.artifactId !== undefined && !id(identity.artifactId))) {
|
||||
throw new Error("Exact Toolkit run ID and both full candidate SHAs are required");
|
||||
}
|
||||
if (identity.mode === "release" && (!attempt(identity.runAttempt) || !id(identity.artifactId) || !sha(identity.leaseGeneration)))
|
||||
throw new Error("Release verification requires an immutable attempt, artifact and lease");
|
||||
}
|
||||
|
||||
export function validateProducerRun(run, identity) {
|
||||
validateExpectedIdentity(identity);
|
||||
const release = identity.mode === "release";
|
||||
if (!run || String(run.id) !== identity.runId || run.repository?.full_name !== repository ||
|
||||
run.head_repository?.full_name !== repository || run.path !== ".github/workflows/ci.yml" ||
|
||||
!["push", "pull_request"].includes(run.event) || run.head_sha !== identity.toolkitSha ||
|
||||
run.status !== "completed" || run.conclusion !== "success" ||
|
||||
!Number.isSafeInteger(run.run_attempt) || run.run_attempt < 1) {
|
||||
run.event !== (release ? "workflow_dispatch" : "pull_request") ||
|
||||
(release && run.head_branch !== "master") || run.head_sha !== identity.toolkitSha ||
|
||||
run.status !== "completed" || run.conclusion !== "success" || !attempt(run.run_attempt) ||
|
||||
(identity.runAttempt !== undefined && run.run_attempt !== identity.runAttempt)) {
|
||||
throw new Error("Private coordinated run provenance or conclusion mismatch");
|
||||
}
|
||||
return run.run_attempt;
|
||||
}
|
||||
|
||||
export function validatePlatformJobs(result) {
|
||||
export function validatePlatformJobs(result, identity, runAttempt, required = evidenceJobs) {
|
||||
if (!result || !Array.isArray(result.jobs) || result.total_count !== result.jobs.length) throw new Error("Incomplete private job evidence");
|
||||
for (const name of ["Coordinated CLI/API (ubuntu-latest)", "Coordinated CLI/API (windows-latest)", "coordinated-receipt"]) {
|
||||
for (const name of required) {
|
||||
const matches = result.jobs.filter((job) => job.name === name);
|
||||
if (matches.length !== 1 || matches[0].status !== "completed" || matches[0].conclusion !== "success") {
|
||||
throw new Error("Both platforms and receipt producer must have succeeded");
|
||||
if (matches.length !== 1 || String(matches[0].run_id) !== identity.runId || matches[0].run_attempt !== runAttempt ||
|
||||
matches[0].head_sha !== identity.toolkitSha || matches[0].status !== "completed" || matches[0].conclusion !== "success") {
|
||||
throw new Error("Required jobs must succeed in this exact source/run/attempt");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function validatePublicReceipt(receipt, identity, runAttempt) {
|
||||
validateExpectedIdentity(identity);
|
||||
if (!receipt || JSON.stringify(Object.keys(receipt).sort()) !== JSON.stringify(keys) ||
|
||||
receipt.schemaVersion !== 1 || receipt.repository !== repository || receipt.runId !== identity.runId ||
|
||||
receipt.schemaVersion !== 2 || receipt.repository !== repository || receipt.runId !== identity.runId ||
|
||||
receipt.runAttempt !== runAttempt || receipt.toolkitSha !== identity.toolkitSha || receipt.cliSha !== identity.cliSha ||
|
||||
JSON.stringify(receipt.platforms) !== JSON.stringify(["Linux", "Windows"]) ||
|
||||
typeof receipt.releaseId !== "string" || typeof receipt.manifestHash !== "string" ||
|
||||
!/^r-[a-f0-9]{64}$/.test(receipt.releaseId) || !/^[a-f0-9]{64}$/.test(receipt.manifestHash)) {
|
||||
// Never echo unexpected input: it may contain private material.
|
||||
!/^r-[a-f0-9]{64}$/.test(receipt.releaseId) || !/^[a-f0-9]{64}$/.test(receipt.manifestHash) ||
|
||||
!id(receipt.sourceRunId) || !attempt(receipt.sourceRunAttempt))
|
||||
throw new Error("Public receipt shape, exact candidate pair or run identity mismatch");
|
||||
if (identity.mode === "release") {
|
||||
if (!id(receipt.sourceArtifactId) || receipt.sourceRunId === receipt.runId || receipt.leaseGeneration !== identity.leaseGeneration)
|
||||
throw new Error("Release receipt must identify original retained source and lease");
|
||||
} else if (receipt.sourceArtifactId !== null || receipt.leaseGeneration !== null || receipt.sourceRunId !== receipt.runId || receipt.sourceRunAttempt !== runAttempt) {
|
||||
throw new Error("PR receipt must identify its own original stage attempt");
|
||||
}
|
||||
return receipt;
|
||||
}
|
||||
|
||||
export function validateReceiptArtifact(result) {
|
||||
export function validateReceiptArtifact(result, identity, runAttempt, base = "coordinated-receipt", maximumSize = 16384) {
|
||||
if (!result || !Array.isArray(result.artifacts) || result.total_count !== result.artifacts.length) throw new Error("Incomplete private artifact metadata");
|
||||
const matches = result.artifacts.filter((artifact) => artifact.name === "coordinated-receipt");
|
||||
if (matches.length !== 1 || matches[0].expired !== false || !Number.isSafeInteger(matches[0].size_in_bytes) ||
|
||||
matches[0].size_in_bytes < 1 || matches[0].size_in_bytes > 16384) throw new Error("Unique unexpired sanitized receipt artifact required");
|
||||
const name = `${base}-${identity.runId}-${runAttempt}`;
|
||||
const matches = result.artifacts.filter((artifact) => artifact.name === name);
|
||||
const artifact = matches[0];
|
||||
if (matches.length !== 1 || !Number.isSafeInteger(artifact.id) || artifact.id < 1 ||
|
||||
(identity.artifactId !== undefined && String(artifact.id) !== identity.artifactId) ||
|
||||
artifact.expired !== false || !Number.isSafeInteger(artifact.size_in_bytes) || artifact.size_in_bytes < 1 || artifact.size_in_bytes > maximumSize ||
|
||||
String(artifact.workflow_run?.id) !== identity.runId || artifact.workflow_run?.head_sha !== identity.toolkitSha ||
|
||||
typeof artifact.digest !== "string" || !/^sha256:[a-f0-9]{64}$/.test(artifact.digest))
|
||||
throw new Error("Unique unexpired exact sanitized artifact required");
|
||||
return artifact;
|
||||
}
|
||||
export function validateLivePullRequest(pr, toolkitSha) {
|
||||
if (!pr || pr.state !== "open" || pr.head?.sha !== toolkitSha || pr.head?.repo?.full_name !== repository ||
|
||||
pr.base?.repo?.full_name !== repository || pr.base?.ref !== "master")
|
||||
throw new Error("Evidence no longer represents the live canonical Toolkit PR head");
|
||||
}
|
||||
export function validateSourceRun(run, receipt) {
|
||||
if (!run || String(run.id) !== receipt.sourceRunId || run.run_attempt !== receipt.sourceRunAttempt ||
|
||||
run.repository?.full_name !== repository || run.head_repository?.full_name !== repository ||
|
||||
run.path !== ".github/workflows/ci.yml" || run.event !== "push" || run.head_branch !== "master" ||
|
||||
run.head_sha !== receipt.toolkitSha || run.status !== "completed" || run.conclusion !== "success")
|
||||
throw new Error("Retained source is not the exact successful master producer attempt");
|
||||
}
|
||||
/** Parse just one bounded regular JSON member. No private archive is extracted to disk. */
|
||||
export function readReceiptArchive(zip, artifact) {
|
||||
if (!Buffer.isBuffer(zip) || zip.length !== artifact.size_in_bytes || zip.length > 16384 || zip.length < 22 ||
|
||||
`sha256:${createHash("sha256").update(zip).digest("hex")}` !== artifact.digest) throw new Error("Receipt archive digest or bound mismatch");
|
||||
const end = zip.length - 22;
|
||||
if (zip.readUInt32LE(end) !== 0x06054b50 || zip.readUInt16LE(end + 4) !== 0 || zip.readUInt16LE(end + 6) !== 0 ||
|
||||
zip.readUInt16LE(end + 8) !== 1 || zip.readUInt16LE(end + 10) !== 1 || zip.readUInt16LE(end + 20) !== 0) throw new Error("One receipt ZIP member required");
|
||||
const central = zip.readUInt32LE(end + 16), centralSize = zip.readUInt32LE(end + 12);
|
||||
if (central + centralSize !== end || central + 46 > end || zip.readUInt32LE(central) !== 0x02014b50) throw new Error("Invalid receipt ZIP index");
|
||||
const nameSize = zip.readUInt16LE(central + 28), extraSize = zip.readUInt16LE(central + 30), commentSize = zip.readUInt16LE(central + 32);
|
||||
const method = zip.readUInt16LE(central + 10), flags = zip.readUInt16LE(central + 8), size = zip.readUInt32LE(central + 24), packed = zip.readUInt32LE(central + 20);
|
||||
const attributes = zip.readUInt32LE(central + 38), unixType = (attributes >>> 16) & 0xf000;
|
||||
const name = zip.subarray(central + 46, central + 46 + nameSize).toString("utf8");
|
||||
if (46 + nameSize + extraSize + commentSize !== centralSize || name !== "coordinated-receipt.json" ||
|
||||
![0, 0x8000].includes(unixType) || (attributes & 0x10) !== 0 || (flags & ~0x808) !== 0 || ![0, 8].includes(method) || size > 4096 || size < 1 ||
|
||||
zip.readUInt32LE(central + 42) !== 0 || zip.readUInt32LE(0) !== 0x04034b50 || zip.readUInt16LE(6) !== flags || zip.readUInt16LE(8) !== method)
|
||||
throw new Error("Receipt archive must contain one bounded regular JSON file");
|
||||
const localNameSize = zip.readUInt16LE(26), localExtraSize = zip.readUInt16LE(28), start = 30 + localNameSize + localExtraSize;
|
||||
if (zip.subarray(30, 30 + localNameSize).toString("utf8") !== name || start + packed > central) throw new Error("Receipt ZIP member mismatch");
|
||||
const descriptorSize = central - (start + packed);
|
||||
if (!(flags & 8)) {
|
||||
if (descriptorSize !== 0 || zip.readUInt32LE(18) !== packed || zip.readUInt32LE(22) !== size || zip.readUInt32LE(14) !== zip.readUInt32LE(central + 16)) throw new Error("Receipt local index mismatch");
|
||||
} else {
|
||||
const descriptor = start + packed;
|
||||
const offset = descriptorSize === 16 && zip.readUInt32LE(descriptor) === 0x08074b50 ? 4 : 0;
|
||||
if (descriptorSize !== 12 + offset || zip.readUInt32LE(descriptor + offset) !== zip.readUInt32LE(central + 16) || zip.readUInt32LE(descriptor + offset + 4) !== packed || zip.readUInt32LE(descriptor + offset + 8) !== size) throw new Error("Receipt data descriptor mismatch");
|
||||
}
|
||||
const bytes = method === 8 ? inflateRawSync(zip.subarray(start, start + packed), { maxOutputLength: 4096 }) : zip.subarray(start, start + packed);
|
||||
if (bytes.length !== size) throw new Error("Receipt size mismatch");
|
||||
return JSON.parse(bytes.toString("utf8"));
|
||||
}
|
||||
export async function verifyCoordinatedEvidence(identity, { get, download }) {
|
||||
validateExpectedIdentity(identity);
|
||||
const runPath = `actions/runs/${identity.runId}`;
|
||||
const run = await get(runPath), selectedAttempt = validateProducerRun(run, identity);
|
||||
const pinned = { ...identity, runAttempt: selectedAttempt };
|
||||
validateProducerRun(await get(`${runPath}/attempts/${selectedAttempt}`), pinned);
|
||||
const eligibility = async () => {
|
||||
if (identity.mode === "release") {
|
||||
for (const [repo, expected] of [[repository, identity.toolkitSha], [cliRepository, identity.cliSha]]) {
|
||||
if ((await get("git/ref/heads/master", repo)).object?.sha !== expected) throw new Error("Selected release master pair changed");
|
||||
}
|
||||
} else {
|
||||
if (!Array.isArray(run.pull_requests) || run.pull_requests.length !== 1 || !Number.isSafeInteger(run.pull_requests[0].number)) throw new Error("One canonical live producer PR required");
|
||||
validateLivePullRequest(await get(`pulls/${run.pull_requests[0].number}`), identity.toolkitSha);
|
||||
}
|
||||
};
|
||||
await eligibility();
|
||||
validatePlatformJobs(await get(`${runPath}/attempts/${selectedAttempt}/jobs?per_page=100`), identity, selectedAttempt);
|
||||
const artifactPath = `${runPath}/artifacts?per_page=100`;
|
||||
const artifact = validateReceiptArtifact(await get(artifactPath), identity, selectedAttempt);
|
||||
const receipt = validatePublicReceipt(readReceiptArchive(await download(artifact), artifact), identity, selectedAttempt);
|
||||
if (identity.mode === "release") {
|
||||
const sourcePath = `actions/runs/${receipt.sourceRunId}`;
|
||||
const sourceIdentity = { runId: receipt.sourceRunId, toolkitSha: identity.toolkitSha, artifactId: receipt.sourceArtifactId };
|
||||
validateSourceRun(await get(sourcePath), receipt);
|
||||
validateSourceRun(await get(`${sourcePath}/attempts/${receipt.sourceRunAttempt}`), receipt);
|
||||
validatePlatformJobs(await get(`${sourcePath}/attempts/${receipt.sourceRunAttempt}/jobs?per_page=100`), sourceIdentity, receipt.sourceRunAttempt,
|
||||
[...evidenceJobs, "upload-content"]);
|
||||
// Metadata only: the retained content itself never enters this public job.
|
||||
validateReceiptArtifact(await get(`${sourcePath}/artifacts?per_page=100`), sourceIdentity, receipt.sourceRunAttempt, "v4-release", 1073741824);
|
||||
validateSourceRun(await get(sourcePath), receipt);
|
||||
}
|
||||
const currentArtifact = validateReceiptArtifact(await get(artifactPath), { ...pinned, artifactId: String(artifact.id) }, selectedAttempt);
|
||||
if (currentArtifact.digest !== artifact.digest || currentArtifact.size_in_bytes !== artifact.size_in_bytes) throw new Error("Receipt artifact changed during download");
|
||||
validateProducerRun(await get(runPath), pinned);
|
||||
await eligibility();
|
||||
return receipt;
|
||||
}
|
||||
|
||||
async function verify() {
|
||||
const identity = {
|
||||
runId: process.env.TOOLKIT_COORDINATED_RUN_ID,
|
||||
toolkitSha: process.env.E2E_TOOLKIT_SHA,
|
||||
cliSha: process.env.E2E_CLI_SHA,
|
||||
runId: process.env.TOOLKIT_COORDINATED_RUN_ID, toolkitSha: process.env.E2E_TOOLKIT_SHA, cliSha: process.env.E2E_CLI_SHA,
|
||||
mode: process.env.E2E_CANDIDATE_KIND || (process.env.GITHUB_EVENT_NAME === "pull_request" ? "pr" : "release"),
|
||||
...(process.env.TOOLKIT_COORDINATED_RUN_ATTEMPT ? { runAttempt: Number(process.env.TOOLKIT_COORDINATED_RUN_ATTEMPT) } : {}),
|
||||
...(process.env.TOOLKIT_COORDINATED_ARTIFACT_ID ? { artifactId: process.env.TOOLKIT_COORDINATED_ARTIFACT_ID } : {}),
|
||||
leaseGeneration: process.env.E2E_LEASE_GENERATION,
|
||||
};
|
||||
validateExpectedIdentity(identity);
|
||||
if (!process.env.GH_TOKEN) throw new Error("Private receipt read credential required");
|
||||
const get = async (path) => {
|
||||
const response = await fetch(`https://api.github.com/repos/${repository}/${path}`, {
|
||||
headers: { authorization: `Bearer ${process.env.GH_TOKEN}`, accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" },
|
||||
signal: AbortSignal.timeout(30000),
|
||||
const get = async (path, repo = repository) => {
|
||||
const response = await fetch(`https://api.github.com/repos/${repo}/${path}`, {
|
||||
headers: { authorization: `Bearer ${process.env.GH_TOKEN}`, accept: "application/vnd.github+json", "X-GitHub-Api-Version": "2022-11-28" }, signal: AbortSignal.timeout(30000),
|
||||
});
|
||||
if (!response.ok) throw new Error(`Cannot verify private evidence: HTTP ${response.status}`);
|
||||
if (!response.ok) throw new Error("Cannot verify private evidence");
|
||||
return response.json();
|
||||
};
|
||||
const runPath = `actions/runs/${identity.runId}`;
|
||||
const attempt = validateProducerRun(await get(runPath), identity);
|
||||
validatePlatformJobs(await get(`${runPath}/jobs?filter=latest&per_page=100`));
|
||||
validateReceiptArtifact(await get(`${runPath}/artifacts?per_page=100`));
|
||||
const root = mkdtempSync(join(tmpdir(), "10x-public-receipt-"));
|
||||
try {
|
||||
// This is the only allowed private download. Never download source, test
|
||||
// logs, wildcard artifacts, manifests, inventories or course bundles here.
|
||||
execFileSync("gh", ["run", "download", identity.runId, "--repo", repository, "--name", "coordinated-receipt", "--dir", root], {
|
||||
stdio: "pipe", timeout: 60000,
|
||||
});
|
||||
const path = join(root, "coordinated-receipt.json");
|
||||
const entries = readdirSync(root);
|
||||
if (entries.length !== 1 || entries[0] !== "coordinated-receipt.json" || !lstatSync(path).isFile() || lstatSync(path).size > 4096) {
|
||||
throw new Error("Receipt artifact must contain one bounded regular JSON file");
|
||||
}
|
||||
const receipt = validatePublicReceipt(JSON.parse(readFileSync(path, "utf8")), identity, attempt);
|
||||
// Reject a rerun started while we downloaded its predecessor's evidence.
|
||||
if (validateProducerRun(await get(runPath), identity) !== attempt) throw new Error("Private run changed during verification");
|
||||
console.log(JSON.stringify(receipt));
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true });
|
||||
}
|
||||
const download = async (artifact) => execFileSync("gh", ["api", `repos/${repository}/actions/artifacts/${artifact.id}/zip`], { stdio: ["ignore", "pipe", "pipe"], timeout: 60000, maxBuffer: 16384 });
|
||||
console.log(JSON.stringify(await verifyCoordinatedEvidence(identity, { get, download })));
|
||||
}
|
||||
|
||||
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
||||
verify().catch(() => {
|
||||
// No raw command/API/parser error: those could include private response bytes.
|
||||
console.error("Coordinated evidence rejected. Require a successful private Toolkit CI run with a sanitized receipt for this exact Toolkit/CLI pair and both platforms. Inspect the private run for details.");
|
||||
process.exitCode = 1;
|
||||
});
|
||||
|
||||
@@ -2,10 +2,11 @@ import { describe, expect, it } from "bun:test";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { createHash } from "node:crypto";
|
||||
import { parse } from "yaml";
|
||||
import {
|
||||
validateExpectedIdentity, validateProducerRun, validatePlatformJobs,
|
||||
validatePublicReceipt, validateReceiptArtifact,
|
||||
validatePublicReceipt, validateReceiptArtifact, validateLivePullRequest, verifyCoordinatedEvidence, readReceiptArchive,
|
||||
} from "../scripts/verify-coordinated-receipt.mjs";
|
||||
|
||||
const identity = { runId: "123", toolkitSha: "a".repeat(40), cliSha: "b".repeat(40) };
|
||||
@@ -13,20 +14,22 @@ const repository = "przeprogramowani/10x-toolkit";
|
||||
const run = () => ({
|
||||
id: 123, repository: { full_name: repository }, head_repository: { full_name: repository },
|
||||
head_sha: identity.toolkitSha, path: ".github/workflows/ci.yml", event: "pull_request",
|
||||
status: "completed", conclusion: "success", run_attempt: 2,
|
||||
status: "completed", conclusion: "success", run_attempt: 2, pull_requests: [{ number: 31 }],
|
||||
});
|
||||
const receipt = () => ({
|
||||
schemaVersion: 1, repository, ...identity, runAttempt: 2,
|
||||
schemaVersion: 2, repository, ...identity, runAttempt: 2,
|
||||
sourceRunId: "123", sourceRunAttempt: 2, sourceArtifactId: null, leaseGeneration: null,
|
||||
platforms: ["Linux", "Windows"], releaseId: `r-${"c".repeat(64)}`, manifestHash: "d".repeat(64),
|
||||
});
|
||||
const jobs = () => ({ total_count: 3, jobs: [
|
||||
const jobs = () => ({ total_count: 7, jobs: [
|
||||
"Coordinated CLI/API (ubuntu-latest)", "Coordinated CLI/API (windows-latest)", "coordinated-receipt",
|
||||
].map((name) => ({ name, status: "completed", conclusion: "success" })) });
|
||||
"coordinated-inputs", "lint-check", "validate", "prepare-coordinated-content",
|
||||
].map((name) => ({ name, run_id: 123, run_attempt: 2, head_sha: identity.toolkitSha, status: "completed", conclusion: "success" })) });
|
||||
|
||||
describe("public CI consumes only verified sanitized private evidence", () => {
|
||||
it("accepts an exact successful private candidate run and both platforms", () => {
|
||||
expect(validateProducerRun(run(), identity)).toBe(2);
|
||||
expect(() => validatePlatformJobs(jobs())).not.toThrow();
|
||||
expect(() => validatePlatformJobs(jobs(), identity, 2)).not.toThrow();
|
||||
expect(validatePublicReceipt(receipt(), identity, 2)).toEqual(receipt());
|
||||
});
|
||||
it.each([
|
||||
@@ -47,18 +50,18 @@ describe("public CI consumes only verified sanitized private evidence", () => {
|
||||
it("rejects failed/skipped Windows or incomplete job evidence even with a success receipt", () => {
|
||||
for (const conclusion of ["failure", "skipped", "cancelled"]) {
|
||||
const input = jobs(); input.jobs[1]!.conclusion = conclusion;
|
||||
expect(() => validatePlatformJobs(input)).toThrow();
|
||||
expect(() => validatePlatformJobs(input, identity, 2)).toThrow();
|
||||
}
|
||||
const input = jobs(); input.jobs.pop();
|
||||
expect(() => validatePlatformJobs(input)).toThrow();
|
||||
expect(() => validatePlatformJobs(input, identity, 2)).toThrow();
|
||||
});
|
||||
it("accepts only one unexpired bounded sanitized artifact", () => {
|
||||
const artifact = { name: "coordinated-receipt", expired: false, size_in_bytes: 512 };
|
||||
expect(() => validateReceiptArtifact({ total_count: 1, artifacts: [artifact] })).not.toThrow();
|
||||
const artifact = { id: 42, name: "coordinated-receipt-123-2", expired: false, size_in_bytes: 512, workflow_run: { id: 123, head_sha: identity.toolkitSha }, digest: `sha256:${"a".repeat(64)}` };
|
||||
expect(() => validateReceiptArtifact({ total_count: 1, artifacts: [artifact] }, identity, 2)).not.toThrow();
|
||||
for (const override of [{ expired: true }, { size_in_bytes: 100000 }, { name: "coordinated-content" }]) {
|
||||
expect(() => validateReceiptArtifact({ total_count: 1, artifacts: [{ ...artifact, ...override }] })).toThrow();
|
||||
expect(() => validateReceiptArtifact({ total_count: 1, artifacts: [{ ...artifact, ...override }] }, identity, 2)).toThrow();
|
||||
}
|
||||
expect(() => validateReceiptArtifact({ total_count: 2, artifacts: [artifact, artifact] })).toThrow();
|
||||
expect(() => validateReceiptArtifact({ total_count: 2, artifacts: [artifact, artifact] }, identity, 2)).toThrow();
|
||||
});
|
||||
it("requires full SHAs and numeric run IDs before any remote access", () => {
|
||||
for (const override of [{ runId: "../other" }, { toolkitSha: "master" }, { cliSha: "" }]) {
|
||||
@@ -91,3 +94,90 @@ describe("public CI consumes only verified sanitized private evidence", () => {
|
||||
expect(notification).toContain('needs.coordinated.result }}" != "success"');
|
||||
});
|
||||
});
|
||||
|
||||
function archive(value: unknown, name = "coordinated-receipt.json", unixType = 0x8000) {
|
||||
const bytes = Buffer.from(JSON.stringify(value));
|
||||
const filename = Buffer.from(name);
|
||||
const local = Buffer.alloc(30);
|
||||
local.writeUInt32LE(0x04034b50); local.writeUInt32LE(bytes.length, 18); local.writeUInt32LE(bytes.length, 22); local.writeUInt16LE(filename.length, 26);
|
||||
const central = Buffer.alloc(46);
|
||||
central.writeUInt32LE(0x02014b50); central.writeUInt32LE(bytes.length, 20); central.writeUInt32LE(bytes.length, 24); central.writeUInt16LE(filename.length, 28); central.writeUInt32LE((unixType * 65536) >>> 0, 38);
|
||||
const end = Buffer.alloc(22);
|
||||
end.writeUInt32LE(0x06054b50); end.writeUInt16LE(1, 8); end.writeUInt16LE(1, 10); end.writeUInt32LE(46 + filename.length, 12); end.writeUInt32LE(30 + filename.length + bytes.length, 16);
|
||||
return Buffer.concat([local, filename, bytes, central, filename, end]);
|
||||
}
|
||||
function remoteFixture() {
|
||||
const zip = archive(receipt());
|
||||
const artifact = { id: 42, name: "coordinated-receipt-123-2", expired: false, size_in_bytes: zip.length, workflow_run: { id: 123, head_sha: identity.toolkitSha }, digest: `sha256:${createHash("sha256").update(zip).digest("hex")}` };
|
||||
const pr = { state: "open", head: { sha: identity.toolkitSha, repo: { full_name: repository } }, base: { ref: "master", repo: { full_name: repository } } };
|
||||
let downloaded = false;
|
||||
const requests: string[] = [];
|
||||
const get = async (path: string) => {
|
||||
requests.push(path);
|
||||
if (path === "pulls/31") return pr;
|
||||
if (path.endsWith("/jobs?per_page=100")) return jobs();
|
||||
if (path.endsWith("/artifacts?per_page=100")) return { total_count: 1, artifacts: [artifact] };
|
||||
return run();
|
||||
};
|
||||
return { zip, artifact, pr, requests, get, wasDownloaded: () => downloaded, download: async () => { downloaded = true; return zip; } };
|
||||
}
|
||||
|
||||
describe("immutable artifact download and exact-attempt verification", () => {
|
||||
it("selects explicit attempt jobs and downloads only the validated artifact ID", async () => {
|
||||
const fixture = remoteFixture(); let selectedId = 0;
|
||||
expect(await verifyCoordinatedEvidence(identity, { get: fixture.get, download: async (artifact) => { selectedId = artifact.id; return fixture.zip; } })).toEqual(receipt());
|
||||
expect(selectedId).toBe(42);
|
||||
expect(fixture.requests).toContain("actions/runs/123/attempts/2/jobs?per_page=100");
|
||||
expect(fixture.requests.some((path) => path.includes("filter=latest"))).toBe(false);
|
||||
});
|
||||
it("rejects an attempt advancing during the actual artifact download", async () => {
|
||||
const fixture = remoteFixture();
|
||||
await expect(verifyCoordinatedEvidence(identity, { download: fixture.download, get: async (path) => {
|
||||
if (path === "actions/runs/123" && fixture.wasDownloaded()) return { ...run(), run_attempt: 3 };
|
||||
return fixture.get(path);
|
||||
} })).rejects.toThrow(/provenance/);
|
||||
});
|
||||
it.each(["run_id", "run_attempt", "head_sha"])("rejects mixed %s job identities", (field) => {
|
||||
const input = jobs(); Object.assign(input.jobs[1]!, { [field]: field === "head_sha" ? "f".repeat(40) : 999 });
|
||||
expect(() => validatePlatformJobs(input, identity, 2)).toThrow();
|
||||
});
|
||||
it("rejects replaced, expired and duplicated artifacts after download", async () => {
|
||||
for (const mutation of ["id", "expired", "duplicate"]) {
|
||||
const fixture = remoteFixture();
|
||||
await expect(verifyCoordinatedEvidence(identity, { download: fixture.download, get: async (path) => {
|
||||
if (fixture.wasDownloaded() && path.endsWith("/artifacts?per_page=100")) {
|
||||
const changed = { ...fixture.artifact, ...(mutation === "id" ? { id: 99 } : mutation === "expired" ? { expired: true } : {}) };
|
||||
const artifacts = mutation === "duplicate" ? [changed, changed] : [changed];
|
||||
return { total_count: artifacts.length, artifacts };
|
||||
}
|
||||
return fixture.get(path);
|
||||
} })).rejects.toThrow();
|
||||
}
|
||||
});
|
||||
it("rejects a PR head changed after tests, forks and closed PRs", () => {
|
||||
const { pr } = remoteFixture();
|
||||
for (const changed of [{ ...pr, state: "closed" }, { ...pr, head: { ...pr.head, sha: "f".repeat(40) } }, { ...pr, head: { ...pr.head, repo: { full_name: "attacker/toolkit" } } }]) {
|
||||
expect(() => validateLivePullRequest(changed, identity.toolkitSha)).toThrow();
|
||||
}
|
||||
});
|
||||
it("accepts release dispatch only with explicit attempt/artifact/lease and rejects PR proof for a release", () => {
|
||||
const releaseIdentity = { ...identity, mode: "release" as const, runAttempt: 2, artifactId: "42", leaseGeneration: "e".repeat(40) };
|
||||
expect(() => validateProducerRun(run(), releaseIdentity)).toThrow();
|
||||
expect(validateProducerRun({ ...run(), event: "workflow_dispatch", head_branch: "master" }, releaseIdentity)).toBe(2);
|
||||
expect(() => validatePublicReceipt(receipt(), releaseIdentity, 2)).toThrow();
|
||||
expect(() => validateExpectedIdentity({ ...releaseIdentity, runAttempt: undefined })).toThrow();
|
||||
});
|
||||
it("does not equate the original CLI SHA with its squash merge SHA", () => {
|
||||
expect(() => validatePublicReceipt(receipt(), { ...identity, cliSha: "f".repeat(40) }, 2)).toThrow();
|
||||
});
|
||||
it("rejects archive extra members, traversal, symlinks, oversized JSON and altered digest without extraction", () => {
|
||||
for (const [name, type, value] of [["../coordinated-receipt.json", 0x8000, receipt()], ["coordinated-receipt.json", 0xa000, receipt()], ["coordinated-receipt.json", 0x8000, "x".repeat(5000)]] as const) {
|
||||
const zip = archive(value, name, type);
|
||||
expect(() => readReceiptArchive(zip, { id: 42, size_in_bytes: zip.length, digest: `sha256:${createHash("sha256").update(zip).digest("hex")}` })).toThrow();
|
||||
}
|
||||
const fixture = remoteFixture();
|
||||
expect(() => readReceiptArchive(fixture.zip, { ...fixture.artifact, digest: `sha256:${"0".repeat(64)}` })).toThrow();
|
||||
const extra = Buffer.from(fixture.zip); extra.writeUInt16LE(2, extra.length - 12);
|
||||
expect(() => readReceiptArchive(extra, { ...fixture.artifact, digest: `sha256:${createHash("sha256").update(extra).digest("hex")}` })).toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user