From 4d11025af16575138ee28aae98bbc3f1a5e750c1 Mon Sep 17 00:00:00 2001 From: Tapish Khandelwal Date: Wed, 9 Sep 2026 00:51:34 +0530 Subject: [PATCH 1/5] fix(release): bump the security policy with the release instead of by hand Codex on #337, P1: `SECURITY.md` declared the `0.5.x` series while that PR sets package.json to 0.6.0, and `tests/docs/public-scope-copy.test.ts` derives the allowed series from package.json. `release.yml` runs the suite *before* `Run Release Please`, so merging #337 as-is fails the suite on master and the workflow stops before creating the tag or any release asset. Reproduced by setting the version to 0.6.0 locally: AssertionError: SECURITY.md names a version series the package is not on (package.json is 0.6.0): expected [ '0.5' ] to deeply equal [] The release PR would have been unmergeable in the only sense that matters: it could not have produced a release. Root cause is the shape this repository keeps hitting -- a hand-maintained duplicate of a fact with a canonical source. `release-please-config.json` already bumps `src/extension/version.ts`, `README.md` and `docs/PUBLICATION_READINESS.md` through `extra-files`; SECURITY.md restated the version and was not on that list, so it could only stay current by someone remembering, and the guard fired during the release rather than before it. SECURITY.md now names the exact release with the same `` annotation the other three use, and joins `extra-files`. The exact form is also more accurate than the series it replaces: `0.5.x` reads as covering 0.5.0, which the row below it ("Previous releases | Not supported; upgrade to the current one") denies. Verified by simulating the release: bumping package.json and SECURITY.md together passes, and bumping package.json alone still fails with the drift message, so the guard keeps its purpose without blocking the release. --- SECURITY.md | 2 +- release-please-config.json | 29 ++++++++++++++++++++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index 0fda4086..52a623e4 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,7 +7,7 @@ privately; do not open a public issue for security findings. | Version | Supported | | -------------------------- | ----------------------------------------- | -| Current release (`0.5.x`) | Security reports and best-effort fixes | +| Current release (`v0.5.1`) | Security reports and best-effort fixes | | | Chrome Web Store release | Published beta; reports and fixes | | Previous releases | Not supported; upgrade to the current one | | Development/nightly builds | No production support | diff --git a/release-please-config.json b/release-please-config.json index 6467273e..e8ad2441 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -21,14 +21,33 @@ { "type": "generic", "path": "docs/PUBLICATION_READINESS.md" + }, + { + "type": "generic", + "path": "SECURITY.md" } ], "changelog-sections": [ - { "type": "feat", "section": "Features" }, - { "type": "fix", "section": "Fixes" }, - { "type": "docs", "section": "Documentation" }, - { "type": "test", "section": "Tests" }, - { "type": "chore", "section": "Maintenance" } + { + "type": "feat", + "section": "Features" + }, + { + "type": "fix", + "section": "Fixes" + }, + { + "type": "docs", + "section": "Documentation" + }, + { + "type": "test", + "section": "Tests" + }, + { + "type": "chore", + "section": "Maintenance" + } ] } } From be7e997ef1c5d50c92771949c3294b750bf53557 Mon Sep 17 00:00:00 2001 From: Tapish Khandelwal Date: Wed, 9 Sep 2026 00:51:34 +0530 Subject: [PATCH 2/5] docs: distinguish the Store package from the release binary for GSTR-2B Codex on #337: both files said the GSTR-2B workbook builder "postdates v0.5.1", is not in a released binary, and that a user "cannot produce it yet". That is false for one of the two distributions Pack ships. Verified before changing the copy: - `src/entrypoints/offscreen/main.ts` imports `buildFiledReturnsGstr2bWorkbook` and branches on `gstr2bWorkbookApplicable` in the production offscreen entrypoint, so the builder is reachable in a built extension. - #337's generated changelog lists "add GSTR-2B invoice workbook (#196)" and the ITC summary sheet (#199) under 0.6.0, so the GitHub release binary carries it. - The Chrome Web Store publication is v0.5.0 and predates it, so a Store user genuinely cannot produce one. Both statements were true of different distributions and the copy collapsed them into one. It now says which is which, and keeps the Store restriction explicit rather than softening it. Both files also hard-coded `v0.5.1` on unannotated lines, two lines away from ones release-please bumps automatically -- the same duplicate that broke SECURITY.md. The corrected text names the Store publication instead, which moves only when a submission is recorded rather than with every release. This adds a positive claim about the GitHub release binary, so it is called out here for review rather than buried: it is evidenced by the entrypoint import above, not inferred from the changelog alone. --- README.md | 7 ++++--- docs/PUBLICATION_READINESS.md | 16 +++++++++++++--- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1b5fc1c5..89397b9e 100644 --- a/README.md +++ b/README.md @@ -82,9 +82,10 @@ files derived from the staged portal JSON already in that run: GSTR-3B working-paper output and contains exactly one sheet, `GSTR-3B Consolidated`. A GSTR-2B full-year run produces its own `full-year-workbook.xlsx` when the -staged JSON carries invoice-level records. This is **source-build output**: it -postdates the published `v0.5.1` package, so a user of the released extension -cannot produce it yet. In a source build it contains: a first `ITC summary` sheet stating +staged JSON carries invoice-level records. This is **not in the Chrome Web Store +package**: the Store publication predates this builder, so a user who installed +from the Store cannot produce it. It is present in the GitHub release binary and +in a source build, where it contains: a first `ITC summary` sheet stating the portal's own availability totals alongside the GSTR-3B table each heading feeds -- as prescribed by FORM GSTR-2B under rule 60(7), not asserted by Pack -- then one sheet per present section (B2B, B2BA, CDNR, IMPG). Each invoice row begins with a reconciliation key of the diff --git a/docs/PUBLICATION_READINESS.md b/docs/PUBLICATION_READINESS.md index 6aa57698..655dc4ff 100644 --- a/docs/PUBLICATION_READINESS.md +++ b/docs/PUBLICATION_READINESS.md @@ -63,9 +63,19 @@ summary` sheet stating the portal's own availability totals and the GSTR-3B spreadsheet unchanged, the run keeps the tidy CSV instead and reports the absence rather than emitting a blank or mislabelled workbook. - **Not in a released binary.** This builder postdates `v0.5.1`. A user of the - published package cannot produce a GSTR-2B workbook, and public copy must not - imply otherwise until a release carries it. The source-controlled Store listing draft + **Not in the Store-published package.** The Chrome Web Store publication is + `v0.5.0`, recorded above, and predates this builder, so a user who installed + from the Store cannot produce a GSTR-2B workbook and public copy must not imply + otherwise. The GitHub release binary is a separate distribution and does carry + it: `src/entrypoints/offscreen/main.ts` imports and invokes the builder in the + production offscreen entrypoint, and `CHANGELOG.md` lists it from the release + that first shipped it. State which distribution a claim describes -- "released" + alone is ambiguous between the two, and this paragraph previously read as + denying both while only one was true. Note also that this sentence names the + Store publication rather than the source version on purpose: the Store version + moves only when a submission is recorded, so it does not drift with every + release the way an unannotated source version does. + The source-controlled Store listing draft names GSTR-2B as supported beta scope and a test binds it to the capability table, but the live dashboard fields still carry superseded wording: they are corrected only once `docs/chrome-web-store/dashboard-closeout.md` records the From c52821eae21c07a1c6359cfe043d6f4f03564ffc Mon Sep 17 00:00:00 2001 From: Tapish Khandelwal Date: Wed, 9 Sep 2026 00:51:34 +0530 Subject: [PATCH 3/5] test(docs): fail when public copy hard-codes the current version Three files named `v0.5.1` by hand beside lines release-please already bumps, and each was wrong the moment a release landed -- SECURITY.md blocked the 0.6.0 release outright, README and PUBLICATION_READINESS told readers a shipped feature was in no binary. Nothing caught the class; the security guard caught one instance, and only during the release it then blocked. Adds a guard that fails when any of the three names the *current* package version without the release annotation. It fires when the hard-coding is written rather than one release later when it has become false. An older version stays legal on purpose -- the Store publication record is one, and it does not move when this package does. The security assertion now binds the exact version rather than a `0.N.x` series, and a second assertion states that SECURITY.md is an `extra-files` entry carrying the annotation, so the binding is automatic rather than remembered. Mutation-tested, each caught with the offending file and line: - current version hard-coded in README -> "README.md hard-codes v0.5.1 ... ['README.md:503']" - annotation stripped from SECURITY.md -> "SECURITY.md hard-codes v0.5.1 ... ['SECURITY.md:10']" - SECURITY.md removed from extra-files -> 1 of 10 fails --- tests/docs/public-scope-copy.test.ts | 61 +++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/tests/docs/public-scope-copy.test.ts b/tests/docs/public-scope-copy.test.ts index a7099935..ee4624da 100644 --- a/tests/docs/public-scope-copy.test.ts +++ b/tests/docs/public-scope-copy.test.ts @@ -291,19 +291,70 @@ describe("public scope copy", () => { // it had drifted two minor versions -- it named 0.3.x while 0.5.1 shipped, so // the file telling users which version receives security fixes named a version // that no longer exists. + // + // It named a `0.N.x` series until the 0.6.0 release, which was a second problem: + // release-please could not bump a series token, so the file could only be kept + // current by someone remembering, and this test failed the release rather than + // preventing the drift. Worse, the series form contradicted the row below it -- + // `0.5.x` reads as covering 0.5.0, which "Previous releases | Not supported" + // denies. It now names the exact current release and is bumped by the release. it("keeps the security policy's supported version bound to the package", async () => { const version = JSON.parse(await read("package.json")).version as string; - const [major, minor] = version.split("."); const security = await read("SECURITY.md"); - const declared = [...security.matchAll(/`(\d+)\.(\d+)\.x`/g)].map((m) => `${m[1]}.${m[2]}`); + const declared = [...security.matchAll(/`v(\d+\.\d+\.\d+)`/g)].map((match) => match[1]); - expect(declared.length, "SECURITY.md declares no supported version series").toBeGreaterThan(0); + expect(declared.length, "SECURITY.md declares no supported version").toBeGreaterThan(0); expect( - declared.filter((series) => series !== `${major}.${minor}`), - `SECURITY.md names a version series the package is not on (package.json is ${version}).`, + declared.filter((candidate) => candidate !== version), + `SECURITY.md names a version the package is not on (package.json is ${version}).`, ).toEqual([]); }); + // The assertion above states the invariant; this one states that nobody has to + // remember it. Without the extra-file entry and the annotation, a release bumps + // package.json, leaves SECURITY.md behind, and fails the suite *inside* the + // release workflow -- before `Run Release Please` reaches the tag, so the release + // does not happen at all. That is how v0.6.0 was blocked. + // Three separate files hard-coded `v0.5.1` next to lines that release-please + // already bumps, and each was wrong the moment a release landed: SECURITY.md + // blocked the 0.6.0 release outright, while README and PUBLICATION_READINESS + // told readers a shipped feature was not in any binary. Naming today's version + // by hand is the defect; the annotation is the fix. This fails at the moment + // someone writes the current version unannotated, rather than one release later + // when it has quietly become false. + it.each(["README.md", "SECURITY.md", "docs/PUBLICATION_READINESS.md"])( + "never states the current version in %s without binding it to the release", + async (file) => { + const version = JSON.parse(await read("package.json")).version as string; + + const unbound = (await read(file)) + .split("\n") + .map((line, index) => ({ line, number: index + 1 })) + .filter(({ line }) => line.includes(`v${version}`)) + .filter(({ line }) => !line.includes("x-release-please-version")) + .map(({ number }) => `${file}:${number}`); + + // An older version may be named deliberately -- the Store publication record + // is one -- because it does not move when this package does. Only the current + // one is guaranteed to go stale. + expect( + unbound, + `${file} hard-codes v${version}; add to that line, or name a version that does not move with the release.`, + ).toEqual([]); + }, + ); + + it("bumps the security policy as part of the release rather than by hand", async () => { + const config = JSON.parse(await read("release-please-config.json")) as { + packages: Record; + }; + + expect(config.packages["."]?.["extra-files"]?.map((file) => file.path)).toContain( + "SECURITY.md", + ); + expect(await read("SECURITY.md")).toContain(""); + }); + // Return-level advertising is too coarse. Every return is advertised, yet // GSTR-3B portal JSON and GSTR-2B details Excel ship without their format // evidence recorded -- so copy could regress to claiming them and every check From 62de8ed471b0e8a50f24b0fc5aa91f79611d91ec Mon Sep 17 00:00:00 2001 From: Tapish Khandelwal Date: Wed, 9 Sep 2026 01:11:45 +0530 Subject: [PATCH 4/5] docs: stop asserting which release binary carries the GSTR-2B workbook Codex on #341, P1: the previous commit overcorrected. The current published release is `v0.5.1`, and `ceb6ada` introduced the builder after it -- the v0.5.1 tree contains zero occurrences of `buildFiledReturnsGstr2bWorkbook`. So the copy saying the GitHub release binary carries it was false for anyone downloading the current release. The entrypoint import proves source availability, not release availability, and I read one as the other. This is the second time this paragraph has been wrong, in opposite directions: first denying every distribution while the source already carried the builder, then asserting the release binary carried it before any release did. A sentence that names which release has a feature is wrong on one side of the release that changes the answer, and this file merges before that release. So it no longer restates the answer. It states the Store restriction, which is fixed and does not move with this package, and directs the reader to compare `CHANGELOG.md` against the release recorded above for the GitHub side. README points at this document rather than carrying a second copy of the status. Worth recording that the previous commit flagged this exact risk in its own message -- "this adds a positive claim ... called out here for review" -- and made the error anyway. Flagging a claim is not the same as verifying it against the artifact it describes. --- README.md | 4 ++-- docs/PUBLICATION_READINESS.md | 25 +++++++++++++------------ 2 files changed, 15 insertions(+), 14 deletions(-) diff --git a/README.md b/README.md index 89397b9e..99d1b3ec 100644 --- a/README.md +++ b/README.md @@ -84,8 +84,8 @@ GSTR-3B working-paper output and contains exactly one sheet, `GSTR-3B Consolidat A GSTR-2B full-year run produces its own `full-year-workbook.xlsx` when the staged JSON carries invoice-level records. This is **not in the Chrome Web Store package**: the Store publication predates this builder, so a user who installed -from the Store cannot produce it. It is present in the GitHub release binary and -in a source build, where it contains: a first `ITC summary` sheet stating +from the Store cannot produce it. See `docs/PUBLICATION_READINESS.md` for which +distributions carry it. It contains: a first `ITC summary` sheet stating the portal's own availability totals alongside the GSTR-3B table each heading feeds -- as prescribed by FORM GSTR-2B under rule 60(7), not asserted by Pack -- then one sheet per present section (B2B, B2BA, CDNR, IMPG). Each invoice row begins with a reconciliation key of the diff --git a/docs/PUBLICATION_READINESS.md b/docs/PUBLICATION_READINESS.md index 655dc4ff..a5c76a98 100644 --- a/docs/PUBLICATION_READINESS.md +++ b/docs/PUBLICATION_READINESS.md @@ -63,18 +63,19 @@ summary` sheet stating the portal's own availability totals and the GSTR-3B spreadsheet unchanged, the run keeps the tidy CSV instead and reports the absence rather than emitting a blank or mislabelled workbook. - **Not in the Store-published package.** The Chrome Web Store publication is - `v0.5.0`, recorded above, and predates this builder, so a user who installed - from the Store cannot produce a GSTR-2B workbook and public copy must not imply - otherwise. The GitHub release binary is a separate distribution and does carry - it: `src/entrypoints/offscreen/main.ts` imports and invokes the builder in the - production offscreen entrypoint, and `CHANGELOG.md` lists it from the release - that first shipped it. State which distribution a claim describes -- "released" - alone is ambiguous between the two, and this paragraph previously read as - denying both while only one was true. Note also that this sentence names the - Store publication rather than the source version on purpose: the Store version - moves only when a submission is recorded, so it does not drift with every - release the way an unannotated source version does. + **Not in the Chrome Web Store package.** The Store publication predates this + builder, so a user who installed from the Store cannot produce a GSTR-2B + workbook and public copy must not imply otherwise. + + For the GitHub release, read `CHANGELOG.md` for the release that first carried + the builder and compare it against the release recorded above. This paragraph + deliberately does not restate the answer. A restatement is wrong on one side of + the release that changes it, and this paragraph has now been wrong on both: + it denied every distribution while the source already carried the builder, and + a correction then asserted the release binary carried it while the published + release still predated the builder and its tree had no + `buildFiledReturnsGstr2bWorkbook` import. Say which distribution a claim + describes, and derive the release side rather than duplicating it. The source-controlled Store listing draft names GSTR-2B as supported beta scope and a test binds it to the capability table, but the live dashboard fields still carry superseded wording: they are From 720099cc3eccac7e00a58a6bfa24d99bca2bc14e Mon Sep 17 00:00:00 2001 From: Tapish Khandelwal Date: Wed, 9 Sep 2026 01:11:45 +0530 Subject: [PATCH 5/5] test(docs): distinguish release-bound versions from fixed distribution records Codex on #341: the guard flagged every occurrence of the current version, including ones that legitimately record another distribution. When the Chrome Web Store catches up to the source version, the Store publication record in `docs/PUBLICATION_READINESS.md` names the same number, and the guard's advice -- add the release annotation -- would make release-please rewrite that record on the next source release even though no new submission happened. The guard would have instructed the maintainer to corrupt a historical fact, and it would have contradicted the distribution policy the same PR introduced. A line naming the current version now declares which kind it is: the release annotation when it tracks this package's release, or `` when it records a version that must not be rewritten. The marker is an escape hatch and no file needs one today, so it is exercised directly rather than left as untested affordance -- tested-but-never-reached is how a guard rots. The scan is now a pure function over text so both paths can be driven from synthetic input. The guard also caught the prose added in the previous commit, which named the current version while explaining why not to name it. That line was rewritten rather than marked. Mutation-tested: - fixed-distribution line without its marker -> flagged with file and line - marker no longer honoured in the helper -> "expected [ 1, 3 ] to deeply equal [ 1 ]" --- tests/docs/public-scope-copy.test.ts | 59 ++++++++++++++++++++++------ 1 file changed, 46 insertions(+), 13 deletions(-) diff --git a/tests/docs/public-scope-copy.test.ts b/tests/docs/public-scope-copy.test.ts index ee4624da..7428c54e 100644 --- a/tests/docs/public-scope-copy.test.ts +++ b/tests/docs/public-scope-copy.test.ts @@ -14,6 +14,25 @@ import { PACK_EXTENSION_DESCRIPTION } from "../../src/extension/manifest-policy" const rootDir = process.cwd(); const read = (relativePath: string) => readFile(path.join(rootDir, relativePath), "utf8"); +// release-please rewrites this line's version on every release of this package. +const RELEASE_BOUND_MARKER = ""; +// This line records some other distribution's version, which release-please must +// not rewrite -- the Chrome Web Store publication moves only on a new submission. +const FIXED_DISTRIBUTION_MARKER = ""; + +// 1-based line numbers naming `version` without declaring how they stay current. +function unmaintainedVersionLines(text: string, version: string): number[] { + return text + .split("\n") + .map((line, index) => ({ line, number: index + 1 })) + .filter(({ line }) => line.includes(`v${version}`)) + .filter( + ({ line }) => + !line.includes(RELEASE_BOUND_MARKER) && !line.includes(FIXED_DISTRIBUTION_MARKER), + ) + .map(({ number }) => number); +} + /** * One claim about what Pack supports lives in eleven files. Correcting it took * seven review rounds, and every round found the same defect: another copy that @@ -322,28 +341,42 @@ describe("public scope copy", () => { // by hand is the defect; the annotation is the fix. This fails at the moment // someone writes the current version unannotated, rather than one release later // when it has quietly become false. + // + // A line may instead be marked fixed. Not every mention of today's version + // tracks *this* release: `docs/PUBLICATION_READINESS.md` records the Chrome Web + // Store publication, which moves only when a submission is recorded. When the + // Store catches up to the source version that record legitimately names the + // same number, and annotating it would make release-please rewrite it on the + // next source release even though no new submission happened -- rewriting a + // historical fact. The marker states which of the two a line is. it.each(["README.md", "SECURITY.md", "docs/PUBLICATION_READINESS.md"])( - "never states the current version in %s without binding it to the release", + "never states the current version in %s without saying how it is maintained", async (file) => { const version = JSON.parse(await read("package.json")).version as string; - const unbound = (await read(file)) - .split("\n") - .map((line, index) => ({ line, number: index + 1 })) - .filter(({ line }) => line.includes(`v${version}`)) - .filter(({ line }) => !line.includes("x-release-please-version")) - .map(({ number }) => `${file}:${number}`); - - // An older version may be named deliberately -- the Store publication record - // is one -- because it does not move when this package does. Only the current - // one is guaranteed to go stale. expect( - unbound, - `${file} hard-codes v${version}; add to that line, or name a version that does not move with the release.`, + unmaintainedVersionLines(await read(file), version).map((line) => `${file}:${line}`), + `${file} names v${version} on a line that says nothing about how it is kept current. Add ${RELEASE_BOUND_MARKER} if the line tracks this package's release, or ${FIXED_DISTRIBUTION_MARKER} if it records another distribution's version that must not be rewritten.`, ).toEqual([]); }, ); + // The marker above is an escape hatch, and an untested escape hatch is where a + // guard rots. No file needs it today -- the Store publication is v0.5.0 while + // the package is on 0.5.1 -- so it is exercised directly instead of waiting for + // the collision that makes it load-bearing. + it("classifies each version mention by how the line says it is maintained", () => { + const text = [ + "bare mention of v1.2.3", + `tracks the release v1.2.3 ${RELEASE_BOUND_MARKER}`, + `Store publication v1.2.3 ${FIXED_DISTRIBUTION_MARKER}`, + "an older v1.2.2 needs no marker", + "no version here at all", + ].join("\n"); + + expect(unmaintainedVersionLines(text, "1.2.3")).toEqual([1]); + }); + it("bumps the security policy as part of the release rather than by hand", async () => { const config = JSON.parse(await read("release-please-config.json")) as { packages: Record;