diff --git a/README.md b/README.md index 1b5fc1c5..99d1b3ec 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. 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/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/docs/PUBLICATION_READINESS.md b/docs/PUBLICATION_READINESS.md index 6aa57698..a5c76a98 100644 --- a/docs/PUBLICATION_READINESS.md +++ b/docs/PUBLICATION_READINESS.md @@ -63,9 +63,20 @@ 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 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 corrected only once `docs/chrome-web-store/dashboard-closeout.md` records the 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" + } ] } } diff --git a/tests/docs/public-scope-copy.test.ts b/tests/docs/public-scope-copy.test.ts index a7099935..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 @@ -291,19 +310,84 @@ 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. + // + // 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 saying how it is maintained", + async (file) => { + const version = JSON.parse(await read("package.json")).version as string; + + expect( + 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; + }; + + 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