Skip to content

Prepare cross-registry CLI/LSP packages, PGO artifacts, and gated publication workflows #471

Description

@vjovanov

Outcome and discussion status

Prepare installable npm and PyPI distributions of the existing grund CLI and optional grund-lsp, with one audited artifact pipeline that also preserves the Cargo release path. A non-publishing rehearsal must prove that the exact candidate artifacts can be installed and behave like the Rust engine before an operator considers publishing them.

This is the shared packaging/build portion of §RM-distribution. Keep human-discussion until the owner settles the choices below and authorizes implementation. This ticket authorizes no release: do not publish to crates.io, npm, PyPI, or TestPyPI; create or push release tags; run a live release workflow; or create a GitHub release as part of completing it. Preparing and reviewing the workflows is the outcome. A later, explicit operator decision must authorize any publication; merging or closing this ticket must not trigger it.

Placement relative to 1.0 remains undecided. This is not a child of #347 and does not add itself to the 1.0 gate.

Why this remains a separate deliverable

The Node and Python binding tickets own their language APIs, conversion/error contracts, and local build metadata. This ticket consumes those outputs and owns target matrices, executable bundling, package selection, release artifacts, cross-install parity and workflow orchestration. The standalone LSP packages have no need to depend on either embedding API. Combining all of this with either binding would make its completion depend on the other language and on unrelated release infrastructure.

#467 owns the adopted 1.0 contract freeze and release evidence. It does not build missing registry distributions. This ticket supplies packaging evidence when appropriate; it neither duplicates that audit nor performs a release. #466 owns the Rust API transition; changes to that API are handled there and in the bindings rather than being introduced by packaging.

Current starting point

Reviewed main at df506acfae6d39169e7e2cdabd2327f669a21c3d:

  • The distribution contract names npm grund-cli and grund-lsp, and PyPI grund and grund-lsp. The CLI and LSP installs are independent.
  • The frontend architecture already has Cargo CLI, core and LSP crates. Node and Python frontends are planned; every frontend depends on the engine, and none depends on another frontend.
  • release.yml publishes Cargo crates in dependency order and builds downloadable CLI artifacts for Linux x64/arm64, macOS x64/arm64 and Windows x64/arm64. It does not contain npm/PyPI packaging jobs. Its artifact packaging copies grund; do not assume equivalent prebuilt LSP packaging already exists.
  • scripts/pgo-build.sh profiles the CLI hot-command workload. Extending it to LSP and native binding products requires explicit training/build design rather than merely labelling an ordinary build PGO.

Scope and package contract

Distribution Required installed surface Assembly owned here
Cargo grund-core, grund, grund-lsp Existing engine crate and independent commands Preserve dependency order, source builds and candidate/version guards while sharing artifact orchestration
npm grund-cli grund executable, npx grund-cli, and the sibling ticket's JS/native API Combine the real CLI binary and native addon with the JS loader and platform package selection
npm grund-lsp Independent grund-lsp executable over stdio Package the real LSP binary and a minimal launcher; no CLI/API installation dependency
PyPI grund grund executable and importable grund API Assemble the sibling ticket's extension/module with the real CLI binary in installable wheels, plus supported source distribution
PyPI grund-lsp Independent grund-lsp executable; usable with pipx install grund-lsp Platform wheels/launchers and an explicit source-build policy without pulling in the CLI distribution

Reuse the existing CLI and server binaries. Launchers must preserve arguments, exit codes, signals, stdin/stdout/stderr, paths containing spaces and Unicode, and executable permissions. LSP stdout remains protocol-only. Packaging must not implement scanning, checking or rendering logic, or introduce frontend-to-frontend Cargo dependencies merely to put binaries in the same distribution.

Platform and compatibility decisions

Before implementation, record one matrix connecting OS/architecture/libc, Rust target, npm platform package, wheel tag, supported runtime range and the runner that actually executes the artifact.

  • Preserve the existing six downloadable CLI targets. The Node contract specifically names macOS arm64/x64, Linux x64/arm64 and Windows x64. Decide Windows arm64 native-addon support explicitly; existing Windows arm64 CLI artifacts alone do not establish addon support.
  • Preserve the pinned Rust compiler and digest-pinned manylinux2014 Linux baseline. State the macOS deployment floor and Windows runtime requirements. Linux musl is not implied by a GNU Linux artifact: either explicitly defer it or agree a separately built and exercised target.
  • Coordinate supported Node versions/N-API level with the Node binding ticket. Coordinate CPython 3.10+ and per-interpreter versus stable-ABI wheels with the Python ticket. Document the exact supported versions, rather than promising every future interpreter automatically. Use maturin and the declared wheel-build orchestration consistently; decide which layer owns each build so the CLI is not silently omitted or rebuilt without its intended profile.
  • Follow the npm platform-package architecture in §AR-bindings.5, including exact-version dependencies and explicit CPU/OS/libc selection. Resolve scope ownership and actual names for every platform package, including the separate LSP family; an example such as @grund-cli/linux-x64 does not prove the scope is controlled.
  • Define supported source fallback behavior and prerequisites, including Python sdist contents and npm source-build invocation. Missing/unsupported native targets produce an actionable error or the documented fallback, never a download of a different version or a silent architecture substitution. Supported prebuilt installs require no Rust toolchain. Do not add opaque runtime downloads outside the package manager's verified artifacts.

Build graph and PGO

Build all registry artifacts from one immutable candidate commit with an explicit manifest of product, version, source SHA, target, runtime/ABI, build toolchain and checksum. CLI/LSP packages within a full release carry the matching engine version per §FS-distribution.4.11. Document how any independently versioned binding-only maintenance permitted by AR-bindings remains compatible with that rule.

Extend the existing PGO path for the prebuilt CLI/LSP/native products, reconcile the current CLI-focused wording in §FS-distribution.4.9 and §DA-pgo-release.2, and specify how the benchmark's shared engine workload trains each product. Profiles must match the compiler/target and relevant code build; do not reuse stale or incompatible profiles. Demonstrate that the packaged payload is the profile-use result. Add any necessary LSP protocol or binding entry-point training deliberately, without inventing a second divergent engine workload.

Keep PGO in manual packaging/pre-release or benchmark paths, outside the regular push/PR feedback loop. Preserve the documented, explicitly identified platform-broken PGO exception with self-checked LTO artifacts; a general build failure must not silently become a successful fallback. Source installs remain ordinary LTO builds.

Publication workflow preparation and ownership

Prepare an auditable separation between build/validate and publish. The build/rehearsal path must need no registry-write credentials and create no tag or release. The future publication path consumes the already verified artifact set rather than rebuilding it differently, remains disabled pending explicit operator authorization, and cannot be reached accidentally through the rehearsal or a normal merge.

  • Preserve existing live registry-name checks and their fail-closed distinction between free names, occupied names and failed queries. Extend inventory for every new platform package. Namespace identity checks are distinct from actual publish authorization; document the owner/team and publishing authority needed for each registry/scope.
  • Keep registry credentials out of build and PR jobs. Choose/document trusted publishing or scoped credentials per registry, restricted permissions and the operator/environment that can authorize publishing. Do not create secrets, change account ownership or relax repository protection as an incidental part of this ticket.
  • Preserve Cargo order (grund-core first; wait for dependency resolvability before dependent crates). For npm, platform dependencies must be available before the umbrella package; PyPI needs the complete intended wheel/sdist set. Define bounded readiness checks, partial-publication recovery, version immutability and rerun behavior. Registry writes cannot be atomic across ecosystems: report a partial release honestly and do not mark it complete while an artifact is missing.
  • Record checksums and source/build provenance for the artifacts and define their verification. Decide whether registry attestations and platform code signing/notarization are required, which identity owns them and how they are supplied. Do not claim a checksum alone proves publisher identity or invent a signing requirement without a decision.
  • Extend version/update tooling and metadata validation for Cargo, npm, Python, lockfiles and bundled binaries. Reject dev versions, mixed engine versions, mismatched commit/tag inputs and incomplete artifact sets before any future write-enabled job. Support-package READMEs, licenses and install examples must describe the actual contents and point at the CLI.

Acceptance criteria for implementation

  • The approved support matrix and package inventory cover every advertised combination and explicitly identify deferred targets, ABI choices, ownership prerequisites and signing/provenance decisions.
  • A credential-free, non-publishing rehearsal produces the complete npm tarball, wheel/sdist, Cargo-package and binary artifact inventory from one recorded candidate; artifacts carry consistent versions, SHA and checksums. No tag, registry upload or GitHub release is produced.
  • Fresh environments install the produced artifacts locally with no Rust toolchain on prebuilt targets. CLI-only installation pulls no LSP; LSP-only installation runs the stdio server without the CLI package. npx/npm executable selection and pip/pipx entry points locate the bundled binary on supported systems.
  • The same representative corpus runs through Cargo-installed/built, npm-installed and wheel-installed CLI binaries on supported operating systems. stdout, stderr, exit status and logical path formatting match byte for byte, including clean/error reports, JSON, Unicode, spaces and unsuccessful queries. Package installation is exercised, not just executables in the Rust build directory.
  • The assembled JS and Python packages load their native modules and run the sibling tickets' API contract fixtures. Rust/Node/Python report data agrees; a common canonical serializer checks the promised JSON/report equivalence without relying on each language's incidental object formatting.
  • Installed LSP packages complete initialize, a representative document/diagnostic exchange and shutdown/exit; launchers introduce no protocol pollution or signal/exit regressions.
  • Each packaged optimized payload has PGO build/training evidence or its specifically approved LTO exception. Source fallback builds and unsupported-target errors follow the documented policy.
  • Workflow verification with mocked registry responses covers name/ownership/query failures, version drift, missing/corrupt artifacts, partial prior uploads, bounded dependency propagation and reruns. No live publish is needed for this evidence.
  • Future publish jobs consume only the validated artifact inventory and require the documented explicit operator gate. Ordinary merges, tag handling added by this work, rehearsal runs, and ticket closure do not activate new registry publication. Existing release behavior is reconciled deliberately rather than weakened to make the rehearsal pass.
  • Distribution specs, architecture, installation guidance and the operator runbook distinguish implemented packaging, remaining account setup and separately authorized publication. They do not advertise unpublished registry versions as available.

Dependencies and exclusions

Depends on #469 (Node napi-rs) and #470 (Python PyO3) binding deliverables for their native modules, API loaders and agreed ABI/runtime metadata. Packaging planning and independent LSP assembly can proceed before both APIs are complete; full installed-package parity cannot. Coordinate changes in #466 so the packages embed the intended supported engine API.

No new CLI command, LSP capability, engine rule or user-facing finding policy is part of this ticket. The 1.0 freeze in #467, actual release/changelog preparation for a selected version, package publication, tag creation, release dispatch and organization-account changes require their own authorization. Full distribution readiness is evidence available to that decision, not permission to take it.

Activity

  1. added
    enhancementNew feature or request
    human-discussionPeople are still discussing this; no machine lays work on it until the label comes off
    and removed
    human-discussionPeople are still discussing this; no machine lays work on it until the label comes off
    on Oct 5, 2026
  2. self-assigned this
    on Oct 5, 2026
  3. vjovanov commented on Oct 5, 2026

    @vjovanov
    CollaboratorAuthor

    Claimed by vjovanov@kung-fu-workstation on 2026-10-05 23:54Z · released on 2026-10-09 10:39Z (completed).

  4. vjovanov commented on Oct 5, 2026

    @vjovanov
    CollaboratorAuthor

    Record · 52 transitions · now completed · last 2026-10-09 09:49Z
    Holder vjovanov@kung-fu-workstation · since 2026-10-05 23:54Z

    1 · supervising → supervising 2026-10-05 23:56Z · `PATH: planned`

    plan runtime/supervision/plan.md (not shown; this comment is at its size limit)

    decision runtime/supervision/decision.md (not shown; this comment is at its size limit)

    2 · ticket.triage · intake → triage 2026-10-05 23:56Z

    ticket runtime/ticket/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.triage.json (not shown; this comment is at its size limit)

    3 · ticket.triage · triage → assessing 2026-10-05 23:56Z

    candidates runtime/triage/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.triage.candidates.md (not shown; this comment is at its size limit)

    4 · ticket.triage · assessing → route-assessment 2026-10-06 00:00Z · `VERDICT: none`

    verdict runtime/triage/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.triage.verdict.md (not shown; this comment is at its size limit)

    5 · ticket.triage · route-assessment → validating 2026-10-06 00:00Z
    6 · ticket.triage · validating → fitting 2026-10-06 00:00Z
    7 · ticket.triage · fitting → route-fit 2026-10-06 00:05Z · `FIT: fits` · `SIZE: complex`

    fit runtime/triage/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.triage.fit.md (not shown; this comment is at its size limit)

    8 · ticket.triage · route-fit → validated 2026-10-06 00:05Z
    9 · ticket.triage · validated → completed 2026-10-06 00:06Z

    validation runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.triage/validation.md (not shown; this comment is at its size limit)

    10 · ticket.agora · agora → cancelled
    11 · supervising → supervising 2026-10-05 23:56Z
    12 · ticket.clarify · clarifying → asking 2026-10-06 00:10Z · `QUESTIONS: ready`

    questions runtime/clarify/questions-1.md

    After this work, a rehearsal could produce a local tarball for npm install --global ./candidate/grund-cli-X.Y.Z.tgz, then exercise the installed grund command without publishing anything (filename illustrative). Today there is no such npm package assembly here. Two facts remain open before proposing that packaging and its future publication workflows; neither question requests permission to release.

    1. Does the no-publication-on-merge boundary include the existing scheduled Cargo publisher? For example, after this PR changes scripts/, Monday's auto-bump.yml can count those paths as substantive, validate a patch candidate, and dispatch release.yml with Cargo publication and GitHub release creation enabled. Answer “include scheduled Cargo” and the proposal must hold automatic publication of a candidate containing this work until a separate operator decision; answer “new publication lane only” and existing scheduled Cargo policy continues, while the new cross-registry publisher remains disabled. The current helper contract explicitly includes live dispatch (§FS-distribution.4.4).

      Why needed: These readings require different workflow scope, and neither permission to release nor a change to existing Cargo policy can be inferred from the ticket.

      Assumption without an answer: Rehearsal stays separate and the new publisher stays disabled; the existing scheduled-Cargo interaction remains unresolved, with implementation and merge held pending your answer. No release or permanent disabling of Cargo automation is authorized.

    … cut at 3000 characters; the whole file is runtime/clarify/questions-1.md

    questions runtime/clarify/questions-2.md

    npm install --global ./candidate/grund-cli-X.Y.Z.tgz (illustrative filename) is the local-install rehearsal this ticket will prepare; today this checkout has no such npm package assembly. The author's answer settles both open matters, so no further clarification is needed before proposing the change. The proposal will preserve the weekly Cargo patch path, which may ship this change through Cargo and a GitHub release, while keeping new npm/PyPI publication unreachable from that path and disabled pending separate operator authorization. Names for both npm platform families remain provisional; npm/PyPI authority and account setup remain unmet prerequisites, with no established signing/notarization identities or existing publication obligations. The assumptions carried into planning are that rehearsal is credential-free and publishes nothing; the current crates.io identity guard remains intact; account, secret and protection changes stay outside this ticket; and technical choices about support, ABI, PGO, provenance and gates are recommendations for proposal review. Full native parity still depends on #469/#470, and this ticket stays outside #347's 1.0 gate. These answers settle scope and owner facts; implementation still requires an approved proposal, and completing this ticket authorizes no release action.

    QUESTIONS: none

    questions runtime/clarify/questions.md

    npm install --global ./candidate/grund-cli-X.Y.Z.tgz (illustrative filename) is the local-install rehearsal this ticket will prepare; today this checkout has no such npm package assembly. The author's answer settles both open matters, so no further clarification is needed before proposing the change. The proposal will preserve the weekly Cargo patch path, which may ship this change through Cargo and a GitHub release, while keeping new npm/PyPI publication unreachable from that path and disabled pending separate operator authorization. Names for both npm platform families remain provisional; npm/PyPI authority and account setup remain unmet prerequisites, with no established signing/notarization identities or existing publication obligations. The assumptions carried into planning are that rehearsal is credential-free and publishes nothing; the current crates.io identity guard remains intact; account, secret and protection changes stay outside this ticket; and technical choices about support, ABI, PGO, provenance and gates are recommendations for proposal review. Full native parity still depends on #469/#470, and this ticket stays outside #347's 1.0 gate. These answers settle scope and owner facts; implementation still requires an approved proposal, and completing this ticket authorizes no release action.

    QUESTIONS: none

    13 · ticket.clarify · asking → clarifying 2026-10-06 00:10Z · `CLARIFICATION: answered`

    answers runtime/clarify/answers.md

    Questions and answers on #471

    Round 1 of 2

    Asked at: #471 (comment)

    After this work, a rehearsal could produce a local tarball for npm install --global ./candidate/grund-cli-X.Y.Z.tgz, then exercise the installed grund command without publishing anything (filename illustrative). Today there is no such npm package assembly here. Two facts remain open before proposing that packaging and its future publication workflows; neither question requests permission to release.

    1. Does the no-publication-on-merge boundary include the existing scheduled Cargo publisher? For example, after this PR changes scripts/, Monday's auto-bump.yml can count those paths as substantive, validate a patch candidate, and dispatch release.yml with Cargo publication and GitHub release creation enabled. Answer “include scheduled Cargo” and the proposal must hold automatic publication of a candidate containing this work until a separate operator decision; answer “new publication lane only” and existing scheduled Cargo policy continues, while the new cross-registry publisher remains disabled. The current helper contract explicitly includes live dispatch (§FS-distribution.4.4).

      Why needed: These readings require different workflow scope, and neither permission to release nor a change to existing Cargo policy can be inferred from the ticket.

      Assumption without an answer: Rehearsal stays separate and the new publisher stays disabled; the existing scheduled-Cargo interaction remains unresolved, with implementation and merge held pending your answer. No release or permanent disabling of Cargo automation is authorized.

    … cut at 3000 characters; the whole file is runtime/clarify/answers.md

    clarification runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.clarify/clarification.md

    Clarification of #471

    CLARIFICATION: answered

    Questions and answers on #471

    Round 1 of 2

    Asked at: #471 (comment)

    After this work, a rehearsal could produce a local tarball for npm install --global ./candidate/grund-cli-X.Y.Z.tgz, then exercise the installed grund command without publishing anything (filename illustrative). Today there is no such npm package assembly here. Two facts remain open before proposing that packaging and its future publication workflows; neither question requests permission to release.

    1. Does the no-publication-on-merge boundary include the existing scheduled Cargo publisher? For example, after this PR changes scripts/, Monday's auto-bump.yml can count those paths as substantive, validate a patch candidate, and dispatch release.yml with Cargo publication and GitHub release creation enabled. Answer “include scheduled Cargo” and the proposal must hold automatic publication of a candidate containing this work until a separate operator decision; answer “new publication lane only” and existing scheduled Cargo policy continues, while the new cross-registry publisher remains disabled. The current helper contract explicitly includes live dispatch (§FS-distribution.4.4).

      Why needed: These readings require different workflow scope, and neither permission to release nor a change to existing Cargo policy can be inferred from the ticket.

      Assumption without an answer: Rehearsal stays separate and the new publisher stays disabled; the existing scheduled-Cargo interaction remains unresolved, with implementation and merge held pending your answer. No release or permanent disabling of Cargo automation is authorized.

    … cut at 3000 characters; the whole file is runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.clarify/clarification.md

    14 · ticket.clarify · clarifying → asking 2026-10-06 00:10Z
    15 · ticket.clarify · asking → completed 2026-10-06 00:10Z
    16 · supervising → supervising 2026-10-05 23:56Z
    17 · ticket.plan · planning → discussing 2026-10-06 00:33Z

    proposal runtime/plan/proposal-1.md

    1. What changes

    After the rehearsal starts its local, read-only npm registry:

    # X.Y.Z is the candidate version; the filename is illustrative.
    npm install -g --registry=http://127.0.0.1:4873 ./candidate/grund-cli-X.Y.Z.tgz
    grund check --format=json tests/e2e/cases/json-report/repo

    Today this checkout cannot produce that tarball. Afterwards it installs without Rust and emits the existing dangling JSON finding, empty stderr and exit 1. Python gets the same command plus import grund; editors get independently installed grund-lsp. This delivers local candidates and a disabled publisher; registry versions remain unpublished.

    2. Design and choices

    A shared artifact builder extends scripts/pgo-build.sh, registry guards and version tooling, reusing real CLI/server binaries and #469/#470's modules, loaders and fixtures. Assembly realizes §AR-bindings.5 and §AR-bindings.6, preserving engine logic and frontend isolation (§AR-bindings.1).

    Recommend Node 22.x/24.x, N-API 8 (ABI contract) and ordinary CPython 3.10–3.14, per-interpreter wheels (cibuildwheel support). Coordinate these proposals with #469/#470; neither has approved them. Defer abi3, free-threaded Python, PyPy, musl and other architectures.

    OS / payload floor Rust target npm suffix, both families Wheel platform Installed-artifact runner
    Linux x64 / glibc 2.17 x86_64-unknown-linux-gnu linux-x64-gnu manylinux2014_x86_64 ubuntu-latest, pinned manylinux container
    Linux arm64 / glibc 2.17 aarch64-unknown-linux-gnu linux-arm64-gnu manylinux2014_aarch64 ubuntu-24.04-arm, pinned manylinux container
    macOS x64 / 11.0 x86_64-apple-darwin darwin-x64 macosx_11_0_x86_64 macos-15-intel
    macOS arm64 / 11.0 aarch64-apple-darwin darwin-arm64 macosx_11_0_arm64 macos-15
    Windows x64 / Windows 10, MSVC 14 runtime x86_64-pc-windows-msvc win32-x64-msvc win_amd64 windows-latest
    Windows arm64 / Windows 11, MSVC 14 runtime aarch64-pc-windows-msvc deferred deferred windows-11-arm: downloadable binaries only

    … cut at 3000 characters; the whole file is runtime/plan/proposal-1.md

    proposal runtime/plan/proposal.md

    1. What changes

    After the rehearsal starts its local, read-only npm registry:

    # X.Y.Z is the candidate version; the filename is illustrative.
    npm install -g --registry=http://127.0.0.1:4873 ./candidate/grund-cli-X.Y.Z.tgz
    grund check --format=json tests/e2e/cases/json-report/repo

    Today this checkout cannot produce that tarball. Afterwards it installs without Rust and emits the existing dangling JSON finding, empty stderr and exit 1. Python gets the same command plus import grund; editors get independently installed grund-lsp. This delivers local candidates and a disabled publisher; registry versions remain unpublished.

    2. Design and choices

    A shared artifact builder extends scripts/pgo-build.sh, registry guards and version tooling, reusing real CLI/server binaries and #469/#470's modules, loaders and fixtures. Assembly realizes §AR-bindings.5 and §AR-bindings.6, preserving engine logic and frontend isolation (§AR-bindings.1).

    Recommend Node 22.x/24.x, N-API 8 (ABI contract) and ordinary CPython 3.10–3.14, per-interpreter wheels (cibuildwheel support). Coordinate these proposals with #469/#470; neither has approved them. Defer abi3, free-threaded Python, PyPy, musl and other architectures.

    OS / payload floor Rust target npm suffix, both families Wheel platform Installed-artifact runner
    Linux x64 / glibc 2.17 x86_64-unknown-linux-gnu linux-x64-gnu manylinux2014_x86_64 ubuntu-latest, pinned manylinux container
    Linux arm64 / glibc 2.17 aarch64-unknown-linux-gnu linux-arm64-gnu manylinux2014_aarch64 ubuntu-24.04-arm, pinned manylinux container
    macOS x64 / 11.0 x86_64-apple-darwin darwin-x64 macosx_11_0_x86_64 macos-15-intel
    macOS arm64 / 11.0 aarch64-apple-darwin darwin-arm64 macosx_11_0_arm64 macos-15
    Windows x64 / Windows 10, MSVC 14 runtime x86_64-pc-windows-msvc win32-x64-msvc win_amd64 windows-latest
    Windows arm64 / Windows 11, MSVC 14 runtime aarch64-pc-windows-msvc deferred deferred windows-11-arm: downloadable binaries only

    … cut at 3000 characters; the whole file is runtime/plan/proposal.md

    18 · ticket.plan · discussing → completed 2026-10-06 00:33Z

    response runtime/plan/response.md

    The author approved the plan by reaction

    Round 1 of 3, posted at: #471 (comment)

    vjovanov reacted 👍 to round 1 and wrote nothing further.
    That is approval of the plan as proposed: implement it as written.
    No part of it was amended, and nothing was added - where the plan left
    something to your judgement, it is still yours.

    19 · supervising → supervising 2026-10-05 23:56Z
    20 · ticket.dependencies · dependencies → completed 2026-10-06 00:54Z · `DEPENDENCIES: clear`

    dependencies runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.dependencies/dependencies.md

    Dependencies for #471

    DEPENDENCIES: clear

    21 · supervising → supervising 2026-10-05 23:56Z
    22 · ticket.specify · specify → opening 2026-10-07 18:11Z · `PR: https://github.com//pull/516`

    contract runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.specify/contract.md

    The contract for #471: specification and failing tests

    Commit 09247c9199dff1bd007fe13be7fdfb83aaeba657 on fix/issue-471 (one
    standalone commit on 18f1a5a695, with no attribution trailer; not pushed). The pull request will come from
    vjovanov:fix/issue-471, opened by opening.

    Spec IDs written or changed

    New:

    • FS-distribution-candidate (docs/functional-spec/FS-distribution-candidate.md):
      the lead, terms, and every leaf below.
      • 1.1 the six-row matrix table, 1.2 every registry row proves every runtime,
        1.3 runtime floors are inspected, 1.4 deferred targets gain no support.
      • 2.1 thirty-nine artifacts, 2.2 npm selection, 2.3 one assembly recipe,
        2.4 READMEs and licence.
      • 3.1 placements, 3.2 the invisible launcher, 3.3 a missing payload is an error,
        3.4 the LSP's stdout.
      • 4.1 npm build:source, 4.2 sdists, 4.3 prerequisites named.
      • 5.1 no credential, 5.2 fresh installs without Rust, 5.3 CLI parity,
        5.4 API parity, 5.5 LSP lifecycle, 5.6 own runner, skipped row named.
      • 6.1 manifest, 6.2 one version, 6.3 binding-only, 6.4 verify, 6.5 receipts.
      • 7.1 per-product training, 7.2 profile keys, 7.3 profile-use build,
        7.4 the Windows arm64 exception.
      • 8.1 disabled publisher, 8.2 identity is not authority, 8.3 verified only,
        8.4 order and readiness, 8.5 reruns and status, 8.6 attestations,
        8.7 the Cargo release cannot reach it.
    • FS-distribution.4.13 — a candidate is rehearsed before anything is published.

    Changed:

    • FS-distribution: the lead, the §1 lead, the npm/PyPI rows of the table (status
      candidate), 1.1, 1.2, 1.3, 2, 3.2, 3.3, the §4 lead, 4.2.6, 4.3, 4.4, 4.5,
      4.8, 4.9, 4.10, 4.11.
    • FS-lsp.2.1 — local candidate install of npm/PyPI grund-lsp, each family
      installed alone.
    • AR-bindings.5, AR-bindings.6, AR-bindings.7.
    • DA-pgo-release.2, AR-benchmarks.1.5, AR-ci.6.
    • docs/functional-spec/README.md lists the new spec under Packaging.
    • tests/integration/functional_spec_coverage_policy.rs drops the
      FS-distribution.2 and FS-distribution.4.11 exceptions (tests now cite both)
      and adds FS-distribution-candidate.terms.

    Tests, by file, name and lane

    Lane ordinary: python -m unittest discover -s tests/integration -p 'test_*.py',
    the gate that pre-commit and push/PR CI run. It is offline, needs no registry and no credential,
    and all of it ran here. Lane rehearsal: tests/integration/rehearsal/run.py, which
    the manual candidate-rehearsal.yml runs on each row's own runner. It is not
    collected by discover: the directory has no __init__.py and no test_* module.

    Ordinary lane

    tests/integration/test_distribution_candidate.py (27 failing results today):

    • MatrixTests: test_the_tool_matrix_is_the_spec_table,
      test_linux_rows_build_in_the_release_images, test_windows_arm64_is_downloads_only.
    • PlanTests: test_the_plan_names_exactly_the_inventory,
      … cut at 3000 characters; the whole file is runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.specify/contract.md

    pr runtime/spec/pr.md

    PR: #516

    23 · ticket.specify · opening → completed 2026-10-07 18:11Z
    24 · supervising → supervising 2026-10-05 23:56Z
    25 · ticket.implement · implement → completed 2026-10-07 20:14Z

    report runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.implement/report.md

    {
    … cut at 3000 characters; the whole file is runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.implement/report.md
    
    </details>
    
    <details><summary><b>26 · supervising → supervising</b> 2026-10-05 23:56Z</summary>
    
    </details>
    
    <details><summary><b>27 · ticket.review-1 · review → completed</b> 2026-10-07 21:09Z</summary>
    
    _findings_ `runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.review-1/findings.md`
    
    ```json
    {
      "round": 1,
      "verdict": "changes-requested",
      "findings": [
        {
          "id": "R1-01",
          "severity": "major",
          "category": "release-safety",
          "file": ".github/workflows/release.yml",
          "line": 448,
          "summary": "The weekly Cargo release now requires `pgo-build.sh --product grund-lsp`, and the new train.py LSP harness inside it, on four rows (macOS x64, macOS arm64, Windows x64, Linux arm64) where it has never run once; if it fails there, nothing is published and the tag is already pushed.",
          "repro": "Code path. release.yml:448 (build-native-pgo, `continue-on-error: ${{ ! matrix.pgo_required }}`, pgo_required true for macos-15-intel, macos-15 and windows-latest) and release.yml:349 (build-linux-gnu-pgo, also the aarch64 row) both run `bash scripts/pgo-build.sh --product grund-lsp`. Before the build matrix runs, prepare-release-ref pushes the tag (release.yml:306). publish-crates `needs` both build jobs, and github-release requires `needs.build-native-pgo.result == 'success'` (release.yml:650). A rerun for the same version re-checks-out the tagged commit, which still holds the failing script. Evidence: runtime/implement-evidence/ holds one replay of this step, release-yml-linux-pgo-step-replay.log (x86_64 container only). Nothing ever ran the LSP step on the other four. Reading the step for those runners, I found no failing line. Git Bash converts the instrumented payload's POSIX path for native Python; find_python takes `python` on Windows and /opt/python/cp312-cp312 in the aarch64 image; train.py sends file:///D:/… URIs, which grund-lsp's `uri.to_file_path()` accepts; the profile is written at a normal exit after `exit`. So this is an unexercised step on the live path, not a reproduced crash. No existing runner covers it either: pre-release-checks.yml runs pgo-build.sh on ubuntu-latest only. A release.yml dry run from this branch also cannot cover it as-is: `python3 scripts/check_release_ramps.py 0.16.2-dev` prints `error: release must look like 0.13.0, got '0.16.2-dev'` (rc 2), and the verify job requires the input version to equal the tree's.",
          "fix": "Before merge, a person authorizes one run that executes `pgo-build.sh --product grund-lsp` on macos-15-intel, macos-15, windows-latest and ubuntu-24.04-arm. The smallest is a fork dispatch of candidate-rehearsal.yml at this head: its build job runs the same script and train.py on every row's runner, and it also settles R1-02. No code change is needed, and the fix round cannot substitute for the run.",
          "criterion": "Author's answer 1 (\"The weekly Cargo patch keeps running as it does today\"); ticket criterion 9 (\"Existing release behavior is reconciled deliberately rather than weakened\")"
        },
        {
          "id": "R1-02",
          "severity": "minor",
          "category": "evidence",
          "file": "tests/integration/rehearsal/run.py",
          "line": 0,
    … cut at 3000 characters; the whole file is runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.review-1/findings.md
    
    </details>
    
    <details><summary><b>28 · supervising → supervising</b> 2026-10-05 23:56Z</summary>
    
    </details>
    
    <details><summary><b>29 · ticket.fix-1 · fix → completed</b> 2026-10-07 21:43Z</summary>
    
    _resolutions_ `runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.fix-1/resolutions.md`
    
    ```json
    {
      "round": 1,
      "resolutions": [
        {
          "id": "R1-01",
          "status": "needs-person",
          "commit": null,
    … cut at 3000 characters; the whole file is runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.fix-1/resolutions.md
    
    </details>
    
    <details><summary><b>30 · supervising → supervising</b> 2026-10-05 23:56Z</summary>
    
    </details>
    
    <details><summary><b>31 · ticket.review-2 · review → completed</b> 2026-10-07 22:33Z</summary>
    
    _findings_ `runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.review-2/findings.md`
    
    ```json
    {
      "round": 2,
      "verdict": "changes-requested",
      "findings": [
        {
          "id": "R2-01",
          "severity": "major",
          "category": "ci",
          "file": "tests/integration/distribution_support.py",
          "line": 366,
          "summary": "`synthetic_candidate` writes manifest.json in text mode but returns the sha256 of the LF text. On Windows the file on disk is CRLF, so the publisher's gate refuses every publishing test at the manifest-digest check, and the required check `cargo test (windows-latest)` is red at both ead9550e5d and b332c7af0a.",
          "repro": "CI at ead9550e5d (run 37685711365, job 113012857654, step 'python tests'): 'Ran 468 tests in 1044.958s / FAILED (failures=12, skipped=23)'. All 12 failures are in test_cross_registry_publisher, each with 'error: --manifest-sha256 de81538c... is not the candidate's manifest (5d13ebae...)', and every receipt 'names manifest de81538c..., not this candidate's 5d13ebae...'. CI at b332c7af0a (run 37691406563, job 113032218350): 'Ran 472 tests in 1424.994s / FAILED (failures=16, skipped=23)', the same 12 plus all 4 new FreshTokenTests. Code path: distribution_support.py:366 `(root / \"manifest.json\").write_text(text, encoding=\"utf-8\")` turns \\n into \\r\\n on Windows, and :367 returns `sha256(text.encode())`, the LF digest. publish.gate -> verify.read_manifest (verify.py:216-218) hashes the bytes on disk, so `actual != digest` (publish.py:40-41), and the receipts, which write_receipts wrote with the LF digest, are refused too. Reproduced locally with Windows text mode simulated: ~/ag/tmp/review471-2/winsim/sitecustomize.py makes every text-mode write with newline=None use '\\r\\n', as Windows does. In a git-archive copy of b332c7af0a, `cd tests/integration && PYTHONPATH=~/ag/tmp/review471-2/winsim python3 -m unittest test_cross_registry_publisher` prints exactly CI's 16 FAIL lines and 'Ran 21 tests / FAILED (failures=16)'. Without the simulation it prints 'Ran 21 tests / OK'. With line 366 changed to `(root / \"manifest.json\").write_bytes(text.encode())`, the simulated run prints 'Ran 21 tests / OK'. rewrite_manifest (:387-388) has the same mismatch, latent because its three callers in test_distribution_candidate ignore the digest. `gh api repos/agent-grounds/grund/rules/branches/main` lists cargo test (windows-latest) as a required status check. The helper came in with this change in 09247c9199.",
          "fix": "Write the manifest as bytes in synthetic_candidate, `(root / \"manifest.json\").write_bytes(text.encode())`, and do the same in rewrite_manifest. Leave verify.read_manifest hashing the bytes on disk (§FS-distribution-candidate.6.1).",
          "criterion": "Ticket acceptance: the change is merged with CI green (required check cargo test (windows-latest) is red at b332c7af0a)"
        },
        {
          "id": "R2-02",
          "severity": "major",
          "category": "correctness",
          "file": "scripts/distribution/assemble.py",
          "line": 97,
    … cut at 3000 characters; the whole file is runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.review-2/findings.md
    
    </details>
    
    <details><summary><b>32 · supervising → supervising</b> 2026-10-05 23:56Z</summary>
    
    </details>
    
    <details><summary><b>33 · ticket.fix-2 · fix → completed</b> 2026-10-07 23:13Z</summary>
    
    _resolutions_ `runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.fix-2/resolutions.md`
    
    ```json
    {
      "round": 2,
      "resolutions": [
        {
          "id": "R2-01",
          "status": "fixed",
          "commit": "c3db923909",
          "note": "tests/integration/distribution_support.py: synthetic_candidate and rewrite_manifest now encode the manifest once, write those bytes with write_bytes and return sha256 of the same bytes. write_receipts is unchanged: its JSON is only parsed. verify.read_manifest still hashes the bytes on disk (§FS-distribution-candidate.6.1). Under review-2's Windows text-mode simulation (PYTHONPATH=~/ag/tmp/review471-2/winsim), test_cross_registry_publisher fails before and passes after. Fail-before, in a git-archive of b332c7af0a: 'Ran 22 tests / FAILED (failures=16, errors=1)'. The 16 FAIL lines are CI's 16, each '--manifest-sha256 f01dcdc4... is not the candidate's manifest (a9c9f021...)'; the 1 error is the ShareTests name this old tree lacks. Pass-after, in a git-archive of HEAD with test_distribution_candidate.ShareTests run alongside: 'Ran 24 tests / OK'. Without the simulation both trees pass the publisher tests."
        },
        {
          "id": "R2-02",
          "status": "fixed",
          "commit": "750a280dd2",
    … cut at 3000 characters; the whole file is runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.fix-2/resolutions.md
    
    </details>
    
    <details><summary><b>34 · supervising → supervising</b> 2026-10-05 23:56Z</summary>
    
    </details>
    
    <details><summary><b>35 · ticket.green · green → green-fix</b> 2026-10-08 00:13Z · `READY: yes`</summary>
    
    _green_ `runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.green/green.md`
    
    # The gate, run 2
    
    Checkout `/home/vjovanov/ag/grund/fix/issue-471` on `fix/issue-471` at `7f5615eb27`; 1 command(s), 12m05s in all.
    
    grund: the installed grund 0.15.1-dev, since the checkout pins none.
    
    | check | command | exit | time |
    |---|---|---|---|
    | 1 | `pre-commit run --all-files` | 0 | 12m05s |
    
    ## 1. `pre-commit run --all-files` - passed
    
    Verdict lines:
    

    grund fmt................................................................Passed
    grund init --check (managed block is current)............................Passed
    lychee...................................................................Passed
    fissile (file size budgets)..............................................Passed
    no Claude attribution boilerplate (staged files).........................Passed

    
    ## What green-fix changed between runs
    
    ### fix-1
    
    - `pre-commit run --all-files`, hook `python-test` (`python scripts/run_python_gate.py`): `test_parity.NodeParityTests.test_complete_data_matches_same_source_rust_oracle` and its `test_node_binding_acceptance.NodeAcceptanceWorkflowTests.test_node_acceptance_is_in_the_complete_local_gate` wrapper failed with `AssertionError: None is not true : shared corpus prerequisite absent: GRUND_BINDINGS_ORACLE` (R1-07, outside this ticket's scope, fixed as the brief authorized). Fixed in 7f5615eb27, "Build the binding oracle for the local Python gate as CI does". When `GRUND_BINDINGS_ORACLE` is unset, `scripts/run_python_gate.py`'s `inputs()` now builds `cargo build --locked -p grund-core --example grund-binding-oracle` with `GRUND_BINDINGS_SOURCE_SHA=$(git rev-parse HEAD)` into `~/ag/tmp/grund-bindings-oracle`, as ci.yml does, citing §FS-distribution.3.0.3. It raises if the build fails or the executable is missing, and leaves a set variable alone, so CI is unchanged. No test, `tests/bindings/`, `.pre-commit-config.yaml`, ci.yml or file the brief froze was touched. `env -u GRUND_BINDINGS_ORACLE TMPDIR=/dev/shm pre-commit run python-test --all-files` now ends `python tests.............................................................Passed` (exit 0).
    
    
    READY: yes
    
    </details>
    
    <details><summary><b>36 · ticket.green · green-fix → green</b> 2026-10-08 00:23Z</summary>
    
    _fix_ `runtime/green/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.green/fix-1.md`
    
    - `pre-commit run --all-files`, hook `python-test` (`python scripts/run_python_gate.py`): `test_parity.NodeParityTests.test_complete_data_matches_same_source_rust_oracle` and its `test_node_binding_acceptance.NodeAcceptanceWorkflowTests.test_node_acceptance_is_in_the_complete_local_gate` wrapper failed with `AssertionError: None is not true : shared corpus prerequisite absent: GRUND_BINDINGS_ORACLE` (R1-07, outside this ticket's scope, fixed as the brief authorized). Fixed in 7f5615eb27, "Build the binding oracle for the local Python gate as CI does". When `GRUND_BINDINGS_ORACLE` is unset, `scripts/run_python_gate.py`'s `inputs()` now builds `cargo build --locked -p grund-core --example grund-binding-oracle` with `GRUND_BINDINGS_SOURCE_SHA=$(git rev-parse HEAD)` into `~/ag/tmp/grund-bindings-oracle`, as ci.yml does, citing §FS-distribution.3.0.3. It raises if the build fails or the executable is missing, and leaves a set variable alone, so CI is unchanged. No test, `tests/bindings/`, `.pre-commit-config.yaml`, ci.yml or file the brief froze was touched. `env -u GRUND_BINDINGS_ORACLE TMPDIR=/dev/shm pre-commit run python-test --all-files` now ends `python tests.............................................................Passed` (exit 0).
    
    </details>
    
    <details><summary><b>37 · ticket.green · green → completed</b> 2026-10-08 00:13Z</summary>
    
    </details>
    
    <details><summary><b>38 · supervising → supervising</b> 2026-10-05 23:56Z</summary>
    
    </details>
    
    <details><summary><b>39 · ticket.describe · describe → completed</b> 2026-10-08 00:46Z</summary>
    
    _description_ `runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.describe/description.md`
    
    ```json
    {
      "title": "Build and rehearse npm and PyPI packages locally without publishing them, and attach grund-lsp archives to releases",
      "body_path": "runtime/supervision/pr-description.md",
      "ticket": "#471",
      "steps_summarized": 25,
      "accounting": "measured",
      "notes": "Title and body are already on PR #516, set through `gh api -X PATCH repos/agent-grounds/grund/pulls/516`, because `gh pr edit` fails on this gh client with the deprecated projectCards GraphQL field. The PR is still a draft. Supervisor, please check one correction: the brief said the live Cargo release changes in exactly three ways, but two scripts the live workflows run also changed, and the body lists them as items 4 and 5. First, scripts/pgo-build.sh was rewritten, so the `grund` PGO build itself, not only the new grund-lsp step, runs rewritten code on linux-arm64, macOS x64, macOS arm64 and Windows x64, where it has never run. That makes R1-01 wider than the grund-lsp step. Second, scripts/check-registry-names.sh, which release.yml's verify job runs, now makes ten more live npm queries. Each fails the release closed if it cannot be read or if another project holds the name. Pre-ship gate: R1-01/R1-02 still need an operator.md answer before ship. If a cross-row candidate-rehearsal.yml run happens, update the row table under 'What is proved, and on which rows' and the 'Open before merge' section. If merging without that run is accepted, the body already claims criteria 3-6 for linux-x64-gnu only. CI run 37708585118 at 7f5615eb27 was still pending when the body was written, and the body says so; do not merge before it is green."
    }
    

    pr-description runtime/supervision/pr-description.md

    Closes #471

    This adds a manual lane that builds every npm and PyPI package of grund and grund-lsp from one commit, installs each one fresh, and checks it against the Rust engine. It also adds a publisher for those packages, and the publisher is disabled. Nothing is published to npm or PyPI, and nothing in this change can publish there. The only workflow that could upload has its publishing job set to a literal if: ${{ false }}. The weekly Cargo release still publishes only the crates and the GitHub release. Everything this change does to it is listed under What reaches the live Cargo release. The part users will see is a grund-lsp archive beside each grund archive.

    Before (on main at 18f1a5a695), nothing in the repository builds an npm or PyPI package:

    $ python3 scripts/distribution/candidate.py matrix
    python3: can't open file '…/scripts/distribution/candidate.py': [Errno 2] No such file or directory

    The weekly release attached grund-<version>-<triple> archives only, and the Install section of docs/user-facing/lsp.md began with "Install the CLI first".

    After (this branch). The listing below is a local linux-x64-gnu row candidate, built with candidate.py build --row linux-x64-gnu before the rehearsal. All four of its payloads are PGO builds:

    $ ls candidate/*/
    candidate/archives/:
    grund-0.16.2-dev-x86_64-unknown-linux-gnu.tar.gz
    grund-0.16.2-dev-x86_64-unknown-linux-gnu.tar.gz.sha256
    grund-lsp-0.16.2-dev-x86_64-unknown-linux-gnu.tar.gz
    grund-lsp-0.16.2-dev-x86_64-unknown-linux-gnu.tar.gz.sha256
    
    candidate/cargo/:
    grund-0.16.2-dev.crate
    grund-core-0.16.2-dev.crate
    grund-lsp-0.16.2-dev.crate
    
    candidate/npm/:
    grund-cli-0.16.2-dev.tgz
    grund-cli-linux-x64-gnu-0.16.2-dev.tgz
    grund-lsp-0.16.2-dev.tgz
    grund-lsp-linux-x64-gnu-0.16.2-dev.tgz
    
    candidate/pypi/:
    grund-0.16.2.dev0-cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
    grund-0.16.2.dev0.tar.gz
    grund_lsp-0.16.2.dev0-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
    grund_lsp-0.16.2.dev0.tar.gz
    
    candidate/receipts/:
    linux-x64-gnu.json
    $ python3 -m venv .venv && .venv/bin/pip install -q --no-index candidate/pypi/grund_lsp-*.whl
    $ ls .venv/bin | grep grund
    grund-lsp
    $ .venv/bin/grund-lsp --version
    grund-lsp 0.16.2-dev

    The language server installs alone, with no grund command beside it; the rehearsal installs it the same way with Rust masked. That one-row candidate verifies, and verify --release refuses it with three named errors: one row rather than the full matrix, scope rehearsal rather than full-release, and a development version.

    … cut at 3000 characters; the whole file is runtime/supervision/pr-description.md

    40 · supervising → supervising 2026-10-05 23:56Z
    41 · ticket.fix-3 · fix → completed 2026-10-09 03:58Z

    resolutions runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.fix-3/resolutions.md

    {
      "round": 3,
      "resolutions": [
        {
          "id": "R3-01",
          "status": "fixed",
          "commit": "c0137805e4",
          "note": "win32-x64-msvc failed at payloads.py:90 (job 113093622446 of run 37710056388) because pgo-build.sh resolves --target-dir with `cd && pwd`, so under Git Bash its paths are MSYS paths (/d/a/_temp/target/...). The evidence JSON carried them as text: Git Bash converts an MSYS path when it is an argument to a native program, which is why the builds, the merge and train.py's arguments worked, but never the contents of a file. Where the conversion lives: in pgo-build.sh, where the evidence is written. The script already held the one helper that turns its paths into what native programs read (`cygpath -m` on a Windows host, for rustc's flags). It is renamed native_path, and the evidence's `profile` and `payload` now go through it. The evidence is the script's interface to native readers: candidate.py keeps the payload it names (§FS-distribution-candidate.7.3), and the rehearsal hands its profile back. Python has no cygpath, and converting in each reader would teach every reader about MSYS. Audit of other Git-Bash paths reaching a native reader: the build job's only bash-produced paths are the script's own. candidate.py build passes native $RUNNER_TEMP paths in, and the merge, train.py and rustc get theirs as converted arguments. The .key file carries no path. assemble and share run on ubuntu-latest. The rehearsal harness's paths come from native Python: setup-python outputs, the oracle step's GITHUB_ENV write, and pathlib. So the evidence's two fields were the only path a file carried. Pinned by EvidenceTests.test_the_evidence_names_files_this_host_opens in tests/integration/test_pgo_build.py (new), which ci.yml runs on every leg through scripts/run_python_gate.py. It runs the real pgo-build.sh with --evidence under stand-in rustc and cargo, and requires the `payload` and `profile` the evidence names to be files this host's Python opens. On windows-latest, --target-dir is a native path that the script turns into /c/...; before this commit the evidence carried that, and Path.is_file() is false for it there. On Linux the test passes either way, so the fail-before holds on windows-latest only, by construction, not by a run of the old code there. Real-runner proof: both the win32-x64-msvc build of run 37873822459 (job 113637669650) and that of the final run built and verified all four payloads past payloads.py:90 (`ok: D:\\a\\_temp/candidate verifies`). Cites §FS-distribution-candidate.5.6 and §FS-distribution-candidate.7.3."
        },
        {
          "id": "R3-02",
          "status": "fixed",
          "commit": "bb3c17adea",
    … cut at 3000 characters; the whole file is runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.fix-3/resolutions.md
    
    </details>
    
    <details><summary><b>42 · supervising → supervising</b> 2026-10-05 23:56Z</summary>
    
    </details>
    
    <details><summary><b>43 · ticket.review-3 · review → completed</b> 2026-10-09 06:16Z</summary>
    
    _findings_ `runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.review-3/findings.md`
    
    ```json
    {
      "round": 3,
      "verdict": "changes-requested",
      "findings": [
        {
          "id": "R3-20",
          "severity": "major",
          "category": "ci",
          "file": "scripts/pgo-build.sh",
          "line": 134,
          "summary": "The required check `cargo test (windows-latest)` is red at 6ace36a404, on the test this round added. `sha256()` passes `sha256sum` a path, and GNU sha256sum escapes a filename that contains a backslash by putting `\\` at the start of its output line. So the evidence's `profile_sha256` begins `\\2b2c…`, and the evidence file pgo-build.sh writes is not valid JSON.",
          "repro": "PR #516 CI run 37889260246 at 6ace36a404, job 113686222234, step 'python tests': 'ERROR: test_the_evidence_names_files_this_host_opens (test_pgo_build.EvidenceTests.test_the_evidence_names_files_this_host_opens)', 'json.decoder.JSONDecodeError: Invalid \\escape: line 8 column 22 (char 418)', 'Ran 490 tests in 1560.577s', 'FAILED (errors=1, skipped=23)'. Line 8 of the evidence is `\"profile_sha256\"`, written at pgo-build.sh:313 as `$(sha256 \"$profdata\")`, and column 22 is the first character of its value. The test passes `--profile` as `str(profile)` (test_pgo_build.py:113), which on Windows is a backslash path. pgo-build.sh:226 keeps it verbatim as `$profdata`; only `--target-dir` is normalised (:72, `cd … && pwd`). Local reproduction on Linux: ~/ag/tmp/review3/mut/repro/r.py runs the test's own stand-ins with a stand-in `cygpath` and a profile under a directory named `native\\dir`. It prints rc 0, then evidence line 8 `\"profile_sha256\": \"\\2b2c81bb82ef3e905b9fec32d7a6d42c9c5437916f2d51db2076281929d36ddf\"`, then 'json.decoder.JSONDecodeError: Invalid \\escape: line 8 column 22 (char 385)'. `sha256sum` given that path prints a line beginning `\\2b2c…`; given the same file on stdin it prints `2b2c…  -`. Ubuntu and macOS pass the test because their temp paths contain no backslash. No production caller passes a backslash `--profile` with `--evidence`: release.yml passes no `--profile` at all, and the rehearsal's two `--profile` calls (profile_payload_provenance.py:153 and :158) pass either the evidence's own `cygpath -m` path or no `--evidence`. So the live release and run 37879010550's candidate are unaffected; what is red is the merge gate. As a consequence, R3-01's own pin errors on Windows before it reaches its assertions, although run 37879010550 proved R3-01 on the real Windows runners.",
    … cut at 3000 characters; the whole file is runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.review-3/findings.md
    
    </details>
    
    <details><summary><b>44 · supervising → supervising</b> 2026-10-05 23:56Z</summary>
    
    </details>
    
    <details><summary><b>45 · ticket.ship · ship → ship-fix</b> 2026-10-09 06:25Z</summary>
    
    _shipped_ `runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.ship/shipped.md`
    
    ```json
    {
      "pr": 516,
      "url": "https://github.com/agent-grounds/grund/pull/516",
      "branch": "fix/issue-471",
      "merged": true,
      "merge_commit": "2919bbed3b3beb07b914c83eeef72f424b4728c7",
      "ci": {
        "cargo test (ubuntu-latest)": "pass",
        "cargo test (macos-latest)": "pass",
        "cargo test (windows-latest)": "pass",
        "commit message attribution gate": "pass",
        "cargo bench (instruction counts)": "pass"
      },
      "fixups": [
        "e8de52d57c \"Hash the PGO evidence's files from stdin so a backslash path stays valid JSON\". sha256() in scripts/pgo-build.sh hashes from stdin in both branches (R3-20), and the digest is unchanged for every backslash-free path.",
        "PR #516 body corrected per R3-11 to R3-19, with the AI-workflow block regenerated, and put on the pull request through gh api. It still opens with `Closes #471`, and the title is unchanged."
      ],
      "main": {
        "commit": "2919bbed3b3beb07b914c83eeef72f424b4728c7",
        "runs": {
          "CI": "success"
        },
        "status": "green"
      },
      "notes": "Merged as 2919bbed3b3beb07b914c83eeef72f424b4728c7; main is green on it (1 run(s))."
    }
    46 · ticket.ship · ship-fix → ship 2026-10-09 06:47Z

    fix runtime/ship/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.ship/fix-1.md

    Called by the supervisor's brief (SHIP: agent, visit 15 attempt 3), which carries the operator's SHIP WITH R3-20 ruling. I did the two things it names: the R3-20 code change as its own commit, then review-3's body corrections R3-11 to R3-19.

    The R3-20 commit, "Hash the PGO evidence's files from stdin so a backslash path stays valid JSON", is e8de52d57c on 6ace36a before the program's rebase. It changes sha256() in scripts/pgo-build.sh and nothing else: sha256sum < "$1" and shasum -a 256 < "$1", with a one-line comment on why. No test, workflow, scripts/distribution/ or tests/integration/rehearsal/ file changed. git diff --stat 6ace36a404 HEAD shows one file, scripts/pgo-build.sh, with 3 insertions and 2 deletions.

    What I checked before handing back:

    • Review-3's reproduction, python3 -I ~/ag/tmp/review3/mut/repro/r.py "$PWD/tests/integration", run from the checkout.
      • At 6ace36a it printed "profile_sha256": "\2b2c81bb…" and then json.decoder.JSONDecodeError: Invalid \escape: line 8 column 22 (char 385).
      • With the change it prints "profile_sha256": "2b2c81bb82ef3e905b9fec32d7a6d42c9c5437916f2d51db2076281929d36ddf", and the evidence parses with no error.
    • The digest for a path with no backslash is unchanged. This is the operator's ground for not rehearsing again. I ran the old and new sha256() functions, taken from git show HEAD~1:scripts/pgo-build.sh and from the working file, on a 1 MiB file ~/ag/tmp/ship471/sample.profdata:
      • old sha256(): b04b3be5752067c7039ccbf1dfe148c54484a6d69e20d588d292298b20270923
      • new sha256(): b04b3be5752067c7039ccbf1dfe148c54484a6d69e20d588d292298b20270923
      • The bare commands give the same digest: sha256sum old and new, and shasum -a 256 old and new. Cargo.lock gives 3c22e61c… in both forms.
      • The same file under native\dir/ gives \b04b3be5… from the old function and b04b3be5… from the new one.
    • sha256() is also called on release.yml's path, at pgo-build.sh:240 and :295, but only on $payload and $pgo_dir/training. Both are built from the normalised target dir (cd … && pwd) and hold no backslash, so their digests are unchanged.
    • (cd tests/integration && TMPDIR=/dev/shm python3 -m unittest test_pgo_build): Ran 8 tests, OK.
    • The distribution slice (test_pgo_build test_distribution_candidate test_distribution_launchers test_cross_registry_publisher test_cross_registry_workflows test_release_workflow test_distribution_packages test_architecture_page_paths): Ran 151 tests, OK.
    • TMPDIR=/dev/shm pre-commit run --all-files exited 1 with two red hooks, neither caused by this change. Log: ~/ag/tmp/ship471/precommit.log.
      … cut at 3000 characters; the whole file is runtime/ship/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.ship/fix-1.md
    47 · ticket.ship · ship → awaiting-checks 2026-10-09 06:25Z
    48 · ticket.ship · awaiting-checks → merging 2026-10-09 06:48Z
    49 · ticket.ship · merging → awaiting-checks 2026-10-09 08:16Z
    50 · ticket.ship · awaiting-checks → merging 2026-10-09 06:48Z
    51 · ticket.ship · merging → watching-main 2026-10-09 08:16Z
    52 · ticket.ship · watching-main → completed 2026-10-09 09:49Z

    Also on disk: runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.plan/proposal.md, runtime/triage/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.triage.evidence.md

    Outcome completed · Verdict: shipped - PR #516 (#516) rebase-merged onto main, tip 2919bbe; #471 closed as completed, and main's CI run 37913725176 on that commit is green.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.triage · Validated: kind feature, verdict none, fit fits, size complex. The export is runtime/exports/github-issues-agent-grounds-grund-471-implement-454b7621.ticket.triage/validation.md.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.agora · Planned path: triage found FIT fits and SIZE complex; no opposing ground or goal/requirement change requires an agora.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.clarify · The author's answers on #471 settled it and nothing further was open. The record is in runtime/clarify/answers.md.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.plan · The author approved the plan on #471 by reaction. Their answer is in runtime/plan/response.md.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.dependencies · Every prerequisite declared for #471 is closed.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.specify · Draft pull request #516 opened from fix/issue-471 with the specification change and the failing test.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.implement · Implemented the cross-registry candidate lane in six commits (fe036e8..ead9550). The ordinary lane is green, and a real PGO linux-x64-gnu candidate passed all seven rehearsal checks on Node 22 and Node 24 and wrote its receipt.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.review-1 · Changes requested: 1 major, 3 minor, 2 nit and 1 adjacent. The major (R1-01) holds the verdict: the weekly Cargo release now requires a grund-lsp PGO step that has never run on four of its required rows. The code itself reads correct, and the contract's tests pass for the reasons the spec gives.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.fix-1 · Fixed R1-03 (fresh registry token before each upload, spec-first, 4 new tests), R1-04 (attestation wording, and the registry attestations listed as unmet) and R1-06 (train.py docstring) in three commits. R1-01 and R1-02 need a person: one fork run of candidate-rehearsal.yml at the final head, whose steps are written in resolutions. R1-05 is deferred and R1-07 adjacent, both as briefed.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.review-2 · Changes requested: 3 major (new: R2-01 and R2-02; carried as needs-person: R1-01) and 2 minor (new: R2-03; carried as needs-person: R1-02). Fix-1's R1-03, R1-04 and R1-06 are verified fixed, but PR #516 is red at b332c7a on two required checks, and the real rehearsal cannot bind the win32-x64-msvc receipt.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.fix-2 · Fixed R2-01 (the Windows test helper's manifest digest), R2-02 (every candidate manifest is now LF bytes, receipts hash them, with a 6.1 sentence first and new ShareTests) and R2-03 (git background writers kept out of the macOS version test); R1-01 and R1-02 still need a person, and R1-05 and R1-07 stay deferred and adjacent.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.green · The gate is green: 1 command(s) passed on 7f5615e in 12m05s.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.describe · Set PR #516's title to "Build and rehearse npm and PyPI packages locally without publishing them, and attach grund-lsp archives to releases" and replaced its body with what landed at 7f5615e; the PR is still a draft.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.fix-3 · Fixed both Windows rows (R3-01 MSYS evidence paths, R3-02 the §FS-distribution-candidate.7.4 correction to the real rustc crash) and the five first-run rehearsal defects behind them (R3-03..R3-07); fork rehearsal run 37879010550 at the exit HEAD 6ace36a is green on every job of every row, nothing was deferred or rejected, and release.yml is untouched.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.review-3 · Changes requested, with 1 major (new: R3-20), 11 minor (new: R3-08; carried and narrowed: R1-01; PR-body corrections: R3-11 to R3-19) and 2 nit (R3-09, R3-10) findings: run 37879010550 proves the candidate on every row and R3-01 to R3-07 are fixed, but this round's own test is red on the required check cargo test (windows-latest) at 6ace36a.

    Outcome github-issues-agent-grounds-grund-471-implement-454b7621.ticket.ship · Pull request #516 merged as 2919bbe.

    Rendered from github-issues-agent-grounds-grund-471-implement-454b7621/tasks/01-ticket.md by scripts/lifecycle-sync.sh.

  5. added
    state:triageFiled; being checked against what is already known
    state:validatingReproducer being built, or fit against the ground being established
    state:decidingThe supervisor is choosing the path
    path:plannedA written plan, discussed on the ticket, before the fix
    state:clarifyingQuestions posted on this ticket wait for its author: words answer them, +1 accepts the assumptions
    and removed
    state:triageFiled; being checked against what is already known
    state:validatingReproducer being built, or fit against the ground being established
    state:decidingThe supervisor is choosing the path
    on Oct 5, 2026
  6. vjovanov commented on Oct 6, 2026

    @vjovanov
    CollaboratorAuthor

    Questions before the plan, round 1 of 2 - please answer on this issue

    This ticket is about to be planned, or argued out in an agora. Before
    either, the few things it leaves open are put to you here. Reply on
    this issue
    with an answer to each, in any form; the work is on hold
    until then.

    Each question states the assumption it would otherwise proceed under,
    so a 👍 on this comment is a complete answer: it accepts every
    assumption as written. A 👎 stops the work and hands the ticket to a
    person. Words beat a reaction where both are present.

    After this work, a rehearsal could produce a local tarball for npm install --global ./candidate/grund-cli-X.Y.Z.tgz, then exercise the installed grund command without publishing anything (filename illustrative). Today there is no such npm package assembly here. Two facts remain open before proposing that packaging and its future publication workflows; neither question requests permission to release.

    1. Does the no-publication-on-merge boundary include the existing scheduled Cargo publisher? For example, after this PR changes scripts/, Monday's auto-bump.yml can count those paths as substantive, validate a patch candidate, and dispatch release.yml with Cargo publication and GitHub release creation enabled. Answer “include scheduled Cargo” and the proposal must hold automatic publication of a candidate containing this work until a separate operator decision; answer “new publication lane only” and existing scheduled Cargo policy continues, while the new cross-registry publisher remains disabled. The current helper contract explicitly includes live dispatch (§FS-distribution.4.4).

      Why needed: These readings require different workflow scope, and neither permission to release nor a change to existing Cargo policy can be inferred from the ticket.

      Assumption without an answer: Rehearsal stays separate and the new publisher stays disabled; the existing scheduled-Cargo interaction remains unresolved, with implementation and merge held pending your answer. No release or permanent disabling of Cargo automation is authorized.

    2. Which registry identities and existing publication obligations can the proposal rely on? The architecture illustrates a CLI/native platform package named @grund-cli/linux-x64, but does not establish control of that scope or name an independent LSP platform scope (§AR-bindings.5). Please identify the controlled npm scope(s) for both families, their publishing account/team, the npm umbrella and PyPI grund/grund-lsp publishing identities, and any already-required signing/notarization identities or publishing environments. For example, “we control these scopes under this team, these PyPI accounts have authority, and this environment/signing identity is required” lets the proposal use those facts; “none established; no existing obligations” makes names provisional and account setup an unmet prerequisite. A registry's available name is not publishing authority; the existing crates.io vjovanov owner guard remains unchanged (§FS-distribution.1.1.1). Please provide identities or requirements only, not credentials.

      Why needed: The proposal cannot name actual package families and publication prerequisites accurately without owner-held facts; new technical choices about credentials, attestations and gates belong in the proposal for review.

      Assumption without an answer: No npm scope control, npm/PyPI publishing authority, or existing signing/environment obligation is established by the available evidence. Intended names and authority/setup requirements remain explicitly unverified prerequisites; no account, secret or protection changes will be made. Silence does not approve implementation or publication.


    Posted by an agent working this ticket. Round 1 of 2; after the last round the answers stand as given.

  7. 13 remaining items

  8. added
    state:shippingPR ready; CI watched; merging
    and removed
    state:implementingPR open; implement, review, fix, green
    on Oct 9, 2026
  9. removed their assignment
    on Oct 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpath:plannedA written plan, discussed on the ticket, before the fix

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions