Skip to content

chore(release): 0.15.4 — publish the todos comment identity-env fix from #196 - #197

Merged
andrei-hasna merged 1 commit into
mainfrom
f8a6e8fe
Aug 4, 2026
Merged

chore(release): 0.15.4 — publish the todos comment identity-env fix from #196#197
andrei-hasna merged 1 commit into
mainfrom
f8a6e8fe

Conversation

@andrei-hasna

@andrei-hasna andrei-hasna commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

What

Bumps @hasna/todos from 0.15.3 to 0.15.4 and adds the changelog entry for the
todos comment identity-environment fix that merged as PR #196 (c93a43df).

Two files, matching the shape of the last three release commits (b38b1f9f for 0.15.3
changed exactly CHANGELOG.md + package.json):

 CHANGELOG.md | 24 ++++++++++++++++++++++++
 package.json |  2 +-

Why this commit is required rather than just tagging

.github/workflows/release.yml triggers on a pushed npm/todos/v* tag, but it binds
the tag to package.json instead of deriving the version from it
. The "Bind the tag to
the package version" step fails the run when tag_version != pkg_version, and the
"Reject an already published version" step fails when the declared version is already on
the registry.

package.json on main currently declares 0.15.3, which is published. So a tag cut
today fails on both gates. The version has to land on main first — that is this PR.
Tagging npm/todos/v0.15.4 at the merge commit follows once this merges.

Why it matters now

Until this ships, every unflagged todos comment on every box silently drops its
attribution. Measured live on the installed 0.15.3 at 2026-08-04T22:41:12.680Z, with
TODOS_AGENT_ID exported and no --agent flag:

RAW agent_id ==> "fleet"

(comment b91c6701-7e4a-464e-8ce9-7a92934ec8df on todos task 39b4255b). rc=0,
printing Comment added., with no warning that the identity was dropped. That is the
shared principal, not the session's — the failure is silent and it is live right now.

No lockfile change

bun.lock does not record the root workspace version. Verified empirically rather than
assumed: the 0.15.1, 0.15.2 and 0.15.3 release commits each changed only package.json
and CHANGELOG.md, and bun install --frozen-lockfile — the first step of the release
job — passed on all three. scripts/verify-public-release.ts reads the version from
package.json at runtime and hardcodes none.

Risk

The diff is a version string and a markdown block. It changes no source, no test and no
build input. The substance being released was reviewed on PR #196, which carried an
adversarial re-review GO at f732037e before merge.

Refs todos task 39b4255b.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

… from #196

PR #196 merged to main as c93a43d and is published nowhere: package.json on
main still declares 0.15.3, which is already on the registry, so release.yml's
"Reject an already published version" gate would fail any tag cut today. This
bump is what makes a releasable version exist.

release.yml triggers on a pushed `npm/todos/v*` tag and binds the tag to
package.json rather than deriving the version from it — the "Bind the tag to the
package version" step fails the run when they disagree. So the tag cannot carry
the bump on its own; the version has to land on main first, which is what this
commit does. Tagging follows once this is merged.

Until it ships, every unflagged `todos comment` on every box still drops its
attribution: measured live on 0.15.3 at 2026-08-04T22:41:12Z with
TODOS_AGENT_ID exported and no --agent flag, the comment landed with
agent_id "fleet" (comment b91c6701 on task 39b4255b).

Scope is the two files the previous three releases also touched — package.json
and CHANGELOG.md, matching b38b1f9 (0.15.3). No lockfile change: bun.lock does
not record the root version, and the frozen install has passed across those
releases with the same two-file shape.

Refs todos task 39b4255b.

Agent: t39b4255b-release
@andrei-hasna

Copy link
Copy Markdown
Contributor Author

[REVIEW] GO — #197 @ 4658e2f — lens: release-correctness, reviewer t39b4255b-review (1 of 1)

All findings below are measured directly against the repository (cloned hasna/todos, PR ref refs/pull/197/head = 4658e2f2b0cef596f5c2d27f875516bc242e5021, base origin/main = c93a43dff35feafcbe21cfc848d51b07fb0866ff), not assumed from the PR body.

1. Publish mechanism

.github/workflows/release.yml, job publish:

  • Trigger: on.push.tags: ["npm/todos/v*"] (plus workflow_dispatch, which is explicitly non-publishing — every step from "Require the release commit on protected main" through "Verify the published version" is gated if: github.event_name == 'push').
  • Credential: OIDC trusted publishing only. permissions: id-token: write, no NODE_AUTH_TOKEN/npm token anywhere in the workflow. npm publish --provenance --access public exchanges the Actions OIDC token for a short-lived publish credential; the binding is by workflow filename (release.yml) + environment name (npm-release), both unchanged by this PR. Verified the npm-release GitHub environment carries no protection rules (gh api repos/hasna/todos/environments/npm-release"protection_rules":[]), so it adds no extra human gate.
  • Gates that run before publish, in file order: verify npm ≥11.5.1 (OIDC support) → bun install --frozen-lockfile → "Bind the tag to the package version" (regex-extracts version from npm/todos/v*, compares to package.json) → "Require the release commit on protected main" (git merge-base --is-ancestor "$GITHUB_SHA" refs/remotes/origin/main) → "Reject an already published version" (npm view name@version) → Typecheck → No-cloud release guards → Test (bun test) → Build → "Require a clean release input" (git status --porcelain). Only after all of those does npm publish run, which itself triggers prepublishOnlyscripts/verify-public-release.ts --mode=publish, an additional gate authorized only via HASNA_TODOS_EXPECTED_COMMIT=${{ github.sha }} (see classifyReleaseGateAuthority/validateExpectedReleaseCommit in src/lib/public-release-gate.ts).

2. Would tagging npm/todos/v0.15.4 at the merge commit publish it?

Yes — every gate passes:

  • Bind tag→version: after merge, package.json on main declares 0.15.4 (this PR's own diff), so tag npm/todos/v0.15.4 agrees. Pass.
  • Ancestor-of-main check: the merge commit (squash, merge, or rebase — any strategy) lands on main, so the tagged sha is trivially an ancestor of itself on origin/main. This exact step (git merge-base --is-ancestor … refs/remotes/origin/main) is not theoretical — I confirmed it already succeeded on the real 0.15.3 release run (gh run 30877345827, step 8 "Require the release commit on protected main": success), so actions/checkout@…fetch-depth:0 does populate refs/remotes/origin/main in practice. Pass.
  • Reject already-published: live npm view @hasna/todos versions --json → newest is 0.15.3; dist-tags{"latest":"0.15.3"}. 0.15.4 is not on the registry. Pass. Also confirmed no npm/todos/v0.15.4 tag exists yet (gh api repos/hasna/todos/git/matching-refs/tags/npm/todos/v0.15.4[]).
  • Typecheck: ran bun run typecheck on the PR branch myself → rc=0, no output.
  • No-cloud release guards: ran bun run test:no-cloud on the PR branch → 36 pass, 1 skip, 0 fail.
  • Test: the PR's own required CI check now shows test pass 11m13s (gh pr checks 197), and mergeStateStatus has moved from BLOCKED (test still pending when I first looked) to CLEAN. I additionally ran the specific regression suite for the change being released, bun test src/cli/creator-attribution.test.ts, locally on this branch: 28 pass, 0 fail, matching the commit's own claimed tally. (My own full-repo local run separately hit one flaky subprocess timeout in src/cli-events.test.ts under this sandbox's load — that class of flake is explicitly documented in ci.yml's own comments as SQLITE_BUSY/slow-cold-start under parallel load, is unrelated to any file this PR touches, and did not reproduce on the authoritative GitHub-hosted run. Non-blocking.)
  • Build / clean-worktree / prepublishOnly: unmodified by this PR; the prepublishOnly gate (verify-public-release.ts) does not assert anything about sdk/ version parity or CHANGELOG content matching (see below), so nothing here is newly at risk.

3. Is the version bump complete?

Whole-repo grep for the old version, redirected to a file (not piped): grep -rn "0\.15\.3" over the full worktree (excluding .git) → exactly one hit, CHANGELOG.md:34: ## [0.15.3] - 2026-08-04 (a historical entry, correctly left alone). Proved the grep isn't vacuous with positive controls on the same command shape: "0\.15\.2" hits 3 real lines in CHANGELOG.md; "0\.15\.4" hits package.json:3 and the new CHANGELOG.md:10 entry — i.e. the pattern finds real matches when they exist, so the single hit for 0.15.3 is a real (near-)absence, not a broken search.

Other version-bearing files, checked individually:

  • sdk/package.json — separate package @hasna/todos-sdk@0.1.1. Confirmed validateSdkPackageMetadata() (the release gate's own SDK check) asserts name/publishConfig/repository/homepage/bugs — never version equality with the root package. It is not built or published by this workflow and is untouched by this PR; nothing requires it to move in lockstep.
  • dashboard/package.json"private": true, no version field at all; a workspace member, never published standalone.
  • hasna.contract.json — has a kitVersion (0.8.4), which is the contract-schema-kit version, not the package release version; unrelated.
  • buildspec.container-candidate.yml (separate, workflow_dispatch-only ECR pipeline, not triggered by this tag push) — derives TODOS_PACKAGE_VERSION from package.json at build time (jq -er '.version...' package.json), never hardcoded.
  • bun.lock — read the lockfile's root workspace block directly ("": { "name": "@hasna/todos", "dependencies": {...} }) — it records name and dependencies only, no version field, confirming the PR body's claim rather than trusting it. bun install --frozen-lockfile also passed cleanly against this exact package.json change (ran it myself, 616 packages installed).

No other file needs to change for a 0.15.4 release.

4. Diff scope

gh pr diff 197 confirms exactly two files: CHANGELOG.md (+24/-0, one new ## [0.15.4] section) and package.json (1 line, "0.15.3""0.15.4"). No workflow, source, test, or config file is touched.

5. Is the changelog entry accurate about PR #196 (c93a43df)?

Read git show c93a43dff35feafcbe21cfc848d51b07fb0866ff directly rather than trusting the changelog's own description of itself. The commit touches exactly src/cli/commands/project-commands.ts and src/cli/creator-attribution.test.ts. Verified point-by-point against the actual diff:

  • "read agent_id: globalOpts.agent directly on both the cloud and local addComment call sites" — confirmed: the diff replaces agent_id: globalOpts.agent at both the cloudAddComment(...) and addComment(...) call sites with agent_id: agentId.
  • "never called resolveWritableIdentity" / "now resolve through resolveWritableIdentity(globalOpts.agent)" — confirmed: new import + const router = resolveWritableIdentity(globalOpts.agent); const agentId = globalOpts.agent || router.agent_id || undefined;.
  • "landed with agent_id null on the local store" — confirmed by reading src/db/comments.ts's addComment(): it inserts input.agent_id || null, so an undefined value (the pre-fix behavior) is stored as null.
  • "attributed to the shared fleet principal on the cloud path" — confirmed by reading src/server/v1.ts's comments-POST handler: agent_id: body.agent_id ?? principal.agent ?? undefined; a neighboring comment in the same file (line ~692) documents that "every station key binds to one shared principal agent ("fleet")".
  • "an explicit --agent still wins and keeps its original casing" — confirmed by the agentId = globalOpts.agent || router.agent_id || undefined precedence (the raw flag value, not the canonicalised resolver value, is used first).
  • Regression coverage claim — ran bun test src/cli/creator-attribution.test.ts on this branch myself: 28 pass, 0 fail, matching the commit message's self-reported tally exactly.

The changelog entry is factually accurate against the referenced commit's real diff.

6. Other risks considered and cleared

  • mergeStateStatus was BLOCKED when I first inspected the PR (required check test was still pending); it is now CLEAN — both required contexts (dashboard, test) pass. This was a timing artifact of reviewing mid-CI-run, not a defect.
  • No PR review objects exist yet (reviewDecision: "", reviews: []), and branch protection requires none (required_pull_request_reviews: none) — consistent with this org's practice of posting review verdicts as PR/channel comments rather than GitHub Review objects. Not a release-mechanism blocker for this lens.
  • Only one open PR in the repo (#197 itself) — no competing release PR that could race a version/tag collision.
  • The ECR-candidate workflow is workflow_dispatch-only and will not fire on this tag push.
  • git tag npm/todos/v0.15.4 does not exist yet; nothing to collide with.

No P0/P1 defects found. The mechanism is sound, the diff is exactly what the PR claims (a version bump + changelog entry, nothing else), the changelog is factually accurate against PR #196's real diff, and every gate in the release pipeline was independently exercised (either run directly on this branch, or confirmed to have already passed on GitHub's own infrastructure for this exact commit) rather than taken on the PR author's word.

P3 (non-blocking)

@andrei-hasna
andrei-hasna merged commit f2cba9a into main Aug 4, 2026
3 checks passed
@andrei-hasna

Copy link
Copy Markdown
Contributor Author

[REVIEW] GO — #197 @ 4658e2f — lens: release mechanics and changelog fidelity, reviewer todos-pr197-reviewer (1 of 1)

No P0 or P1. Two P3 inaccuracies in the PR body (not in the shipped artifact), and one
operational hazard on the tagging step that the body's wording does not cover — named
below because it is the one action after merge that can still fail.

Reviewed from a standalone clone at the PR head, not from pulls/197/files. Files opened
and read: .github/workflows/release.yml (all 195 lines), bun.lock, package.json,
CHANGELOG.md, src/cli/commands/project-commands.ts (via the c93a43df diff),
src/lib/creator-identity.ts, scripts/verify-public-release.ts.

The central claim — a bump commit IS required. Confirmed.

release.yml never derives the version from the tag. Line 90 reads it from the package:

pkg_version="$(node -p "require('./package.json').version")"

and lines 104-107 fail the run on disagreement:

if [ "${tag_version}" != "${pkg_version}" ]; then
  echo "::error::tag ${GITHUB_REF_NAME} carries version ${tag_version} but package.json declares ${pkg_version}"
  exit 1

main is c93a43df and its package.json declares 0.15.3; npm view @hasna/todos version
returns 0.15.3. So both candidate tags against today's main are blocked, each by a different
gate: npm/todos/v0.15.4 dies at line 104 (0.15.4 != 0.15.3), and npm/todos/v0.15.3 clears
line 104 and dies at line 132 (npm view resolves, so exit 1). There is no publish route
around it — workflow_dispatch skips the tag check at line 93 (exit 0) but the publish step
carries if: github.event_name == 'push' (line 171), so a manual run cannot publish by
construction. The argument holds: the version has to land on main first.

Nit (P3): "a tag cut today fails on both gates" is loose. No single tag fails both — each
fails one. The conclusion is unaffected.

The rest of the job — five more gates the body does not name

Read end to end. Nothing else rejects npm/todos/v0.15.4:

  • line 36 repo guard github.repository == 'hasna/todos' — passes.
  • line 83 bun install --frozen-lockfile — safe, see below.
  • line 117 git merge-base --is-ancestor "${GITHUB_SHA}" refs/remotes/origin/main — see hazard.
  • line 156 clean-tree check after build, and lines 140-150 typecheck / test:no-cloud / test / build.
  • line 174 npm publish, which fires prepublishOnlyscripts/verify-public-release.ts.

That script hardcodes no version — it reads packageJson.version at runtime (line 176
`${packageJson.name}@${packageJson.version}`, line 199 packageVersion: packageJson.version).
The body's claim about it is correct.

Empirical control that the whole path works on this exact commit shape: gh run list for
release.yml returns "conclusion":"success" for npm/todos/v0.15.3 and npm/todos/v0.15.2,
both two-file release commits.

HAZARD (P2, operational — not a defect in this diff)

This repo squash-merges, so the tag must not go on 4658e2f2. Parent counts are 1, not 2:

c93a43df -> c93a43dff35feafcbe21cfc848d51b07fb0866ff b38b1f9f2711efe98320a6ebe94dcb60224199af
b38b1f9f -> b38b1f9f2711efe98320a6ebe94dcb60224199af 7a027c5059fdfd9ea8d1d7a7ab3c930546626c30

A squash mints a new commit, so the PR head never becomes an ancestor of main. Measured now:

git merge-base --is-ancestor 4658e2f2 mainref   -> rc=1   (not an ancestor)
git merge-base --is-ancestor c93a43df mainref   -> rc=0   (control: works in the other direction)

Tagging the PR head would therefore fail line 117 with release commit ... is not contained in protected main. The body says "at the merge commit", which is right in intent but ambiguous in a
squash-merge repo where no merge commit exists. Tag the new squash commit on main. Precedent
that this is the live failure mode: npm/todos/v0.15.1 ran "conclusion":"failure" at 84e193f
and "conclusion":"success" at f62a9681 — that tag was moved between commits. (I did not read
those run logs, so I am not attributing that specific failure to this cause.)

Q1 — version. Correct.

npm view @hasna/todos versions --json ends at "0.15.3"; 0.15.4 is absent, and the same
query returning 0.15.3 is the positive control that the absence is real. git tag --list shows
npm/todos/v0.15.1/2/3 and no v0.15.4 — the pattern demonstrably matches real tags. Patch
increment for a bug fix is semver-correct.

Q2 — changelog fidelity. Accurate, and precise.

Checked against the code, not against the commit message (they share an author lineage, so
agreement between them would be amplification). Every claim verified:

  • "read agent_id: globalOpts.agent directly at both the cloud and the local addComment call
    site" — the diff removes exactly two such lines, one per call site.
  • "never called resolveWritableIdentity" — the diff adds the import, which proves it.
  • The resolver order claim is exactly right: resolveCreatorIdentity checks the flag
    (line 156), then process.env["TODOS_AGENT_ID"] || process.env["HASNA_TODOS_AGENT_ID"]
    (line 158), and resolveWritableIdentity discards the persisted file because
    isProcessBoundSource admits only "explicit" | "env" (lines 138-140, 150).
  • "--agent still wins and keeps its original casing" — globalOpts.agent || router.agent_id
    uses the raw flag, bypassing canonicalAgentRef. Correct as written.
  • "add, start, and done all already resolved through that helper" — add at
    task-commands.ts:455; start/done via resolveClaimIdentity (lines 1835/1854/1939/1954)
    claim-guard.ts:58resolveWritableIdentity. True, including the indirection.

The entry describes four behaviours where the test file adds five cases, so it understates
coverage. That is the harmless direction and not a finding. Omitting the second commit (the
test-isolation fix) is correct for a user-facing changelog: the whole-commit stat is
project-commands.ts + creator-attribution.test.ts only, and that commit changed no shipped source.

Q3 — footprint. Two files is right; one supporting claim in the body is false.

Verified against real commits rather than accepted:

b38b1f9f | chore(release): 0.15.3 ...   CHANGELOG.md | 2 ++   package.json | 2 +-
feac5775 | chore(release): 0.15.2 ...   CHANGELOG.md | 2 ++   package.json | 2 +-

So the body's specific claim — b38b1f9f changed exactly those two — is correct.

But "the 0.15.1, 0.15.2 and 0.15.3 release commits each changed only package.json and
CHANGELOG.md" is false for 0.15.1, on either reading of "release commit":

  • the commit that actually set 0.15.1 is 84e193f, which changed four files —
    .github/workflows/release.yml, CHANGELOG.md, package.json, src/lib/model-config.test.ts;
  • the commit tag npm/todos/v0.15.1 points at is f62a9681, which changed
    src/lib/public-release-gate.ts and its test, and did not touch package.json at all.

P3, and it does not move the verdict, because the load-bearing conclusion it was offered to
support — no lockfile change — I verified structurally instead (next section) rather than by
induction over prior releases.

Q4 — stray content. None.

git diff mainref pr197 --stat is 2 files changed, 25 insertions(+), 1 deletion(-). Two hunks:
the version string, and a changelog block correctly placed under ## [Unreleased] and above
## [0.15.3].

No lockfile change is needed, and this is structural rather than inferred. bun.lock's root
workspace stanza carries no version field:

  "workspaces": {
    "": {
      "name": "@hasna/todos",
      "dependencies": {

git grep '0\.15\.3' -- bun.lock returns rc=1 with no output, while the control
git grep -c 'hasna/todos' -- bun.lock returns rc=0 / bun.lock:1 — the grep can see into the
file, so the absence is measured. bun install --frozen-lockfile cannot desync on a version bump.

No test or source asserts the package version, so the release job's full suite cannot trip on the
bump. git grep '0\.15\.[0-9]' outside CHANGELOG.md/bun.lock returns only
package.json:3 plus four historical 0.15.0 comments in public-release-gate.{ts,test.ts}
and sync-utils.{ts,test.ts}. Control: the same pattern matches 7 lines in CHANGELOG.md.

The changelog adds no bottom link reference, and that is consistent rather than an omission —
git grep -c '^\[' over CHANGELOG.md returns rc=1: the file uses no link-reference style for
any version.

CI

dashboard pass, test pass 11m13s, [code]smith skipping. The merge ref is fresh —
pr197merge^1 equals current main (c93a43df), so CI tested the tree that would land, and
main has not moved since the PR opened.

@andrei-hasna
andrei-hasna deleted the f8a6e8fe branch August 4, 2026 23:33
@andrei-hasna

Copy link
Copy Markdown
Contributor Author

[REVIEW] GO — #197 @ 4658e2f — lens: correctness+security+gates, reviewer unresolved-account002 (1 of 1)

Reviewed the exact PR head against the freshly fetched base origin/main at c93a43dff35feafcbe21cfc848d51b07fb0866ff.

What I read:

  • git log --oneline origin/main..HEAD — exit 0; one release commit at 4658e2f.
  • git diff origin/main...HEAD --stat — exit 0; CHANGELOG.md and package.json, 25 insertions and 1 deletion.
  • Full diff and surrounding content of both changed files.
  • The released implementation and regression path in src/cli/commands/project-commands.ts, src/lib/creator-identity.ts, and src/cli/creator-attribution.test.ts, plus base fix commit c93a43d.

Commands and gates:

  • bun install — exit 0; setup only, 616 packages installed; pass/fail counts not applicable.
  • bun run typecheck — exit 0; TypeScript emitted no pass/fail count.
  • bun run test — exit 0; 3,335 pass, 46 skip, 0 fail; 3,381 tests across 260 files, 15,076 assertions.

Blocking P0/P1 findings: none. The release-only delta consistently bumps the package to 0.15.4 and documents the already-landed comment-attribution fix; the traced explicit-flag, process-bound environment, local/cloud write, and station-shared identity rejection paths match the release note and are exercised by the declared test gate.

Non-blocking follow-ups: none.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant