Repository navigation
Fix sec issues #388–#395 and remove sec-base: diagnostics, CI gates, Bun pin, XBRL scaling, primary_doc, fixture fetches - #392
Merged
Conversation
The single body parsed four fixtures (the largest 7 MB, ~5 s together on an idle machine) and crossed the 30 s test timeout under full-suite load. Split with it.each, no case comes near the limit, and a timeout or a loss names the fixture it came from. Fixes #391 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdP6Nwc3vwqfC3vq8urgqT
CI ran five of the six release gates and never inspected the tarball or started the built binaries; a widened `files` or a binary that dies on --help was found only at release time. `scripts/releaseChecks.ts` is now the one gate list both `release-checks` and CI run: format-check, lint, typecheck, build, prepack-check, smoke-bins, test. - smoke-bins starts each built binary with no SEC env, no .env.local and an empty cwd, and requires --help and --version to exit 0. It covers dist/sec.js; dist/libs-cli.js still dies there and joins the list when its boot is made lazy. - prepack-check refuses any src/ path, not only mock_data, so a source leak too small for the size ceiling still fails. - Bun is pinned once, packageManager bun@1.4.3, and setup-bun reads it from package.json instead of installing latest: typecheck is bun check, so a Bun release could turn an unchanged commit red. engines.bun and @types/bun are checked against it by toolchainPins.test.ts. - bun-latest.yml runs the same gates weekly on the newest Bun, gating nothing, so drift is seen before the pin moves. Fixes #389 Fixes #390 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdP6Nwc3vwqfC3vq8urgqT
43 console.log/warn/error calls under src/task, src/sec, src/storage and src/config ran while the Ink progress frame was up. The frame erases by line count, so a write underneath it shifted the count and the tail of the message was overwritten; a failing CIK during `sec update` left a garbled frame and an unreadable line. - src/util/diagnostics.ts (reportDiagnostic / reportDryRun): buffered inside withDeferredDiagnostics and flushed once the run settles, capped at 200 lines keeping both ends; console.warn outside a run. - runWorkflowCli runs every graph inside withDeferredDiagnostics. - Every site converted: failures and summaries to reportDiagnostic, "Would ..." lines to reportDryRun, bulk-download progress to the task's updateProgress. The console.error before a rethrow is gone; setup's "Wrote .env.local" lines are printed by the command from the task's output. unzip's per-member listing no longer goes to the terminal; its stderr is kept for the error. - The Postgres cik_names writer reports one total of collapsed duplicate CIKs instead of a line per 30,000-row slice. - consoleDiagnostics.test.ts pins console writes per file over every tree but src/cli (the sink is the one entry), exact in both directions. - runWorkflow.diagnostics.test.ts drives a failing CIK through fetchAndStoreSubmission inside runWorkflowCli and asserts the console saw nothing while the frame was up and the CIK's message after it. Fixes #388 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdP6Nwc3vwqfC3vq8urgqT
normalizeFactValue multiplied Number(text) by 10 ** scale, which rounds twice: 0.07 at scale 2 became 7.000000000000001, and across two-decimal values at the scales filings use about one in ten missed by an ulp. Those land in xbrl_facts.value_numeric, which is compared exactly. The scale is now folded into the literal's exponent and parsed once, so the result is Number(text + "e" + scale). Number() also read "" and whitespace as 0 (and 0x10 as 16). Numeric text must now be a decimal literal, optionally with an exponent; anything else is null. Fixes #394 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdP6Nwc3vwqfC3vq8urgqT
primary_doc is filer-written. The bootstrap and cache-read paths ran it through sanitizePrimaryDoc, but the two fetch-on-miss callers - the conversion sweep and `sec read --fetch` - passed it straight to SecFetchAccessionDocTask, which spliced it into the request URL. A name with ../, ? or # reached a different sec.gov path, and the request was spent against the rate budget before the cache write's guard ran. - sanitizePrimaryDoc also refuses ?, # and % (a query, a fragment, an escape a server may decode) and control characters; safePrimaryDocName is its predicate form. - SecFetchAccessionDocTask validates fileName in inputToUrl and inputToFileName and percent-encodes the segment. The constructor builds the URL, so a refused name throws a TaskError before anything is queued. - conversionCandidates drops an unsafe name and conversionFetchFileName returns undefined for it; the task reports the filing and fetches nothing. A form read as the whole submission still fetches its .txt, which never uses the name, and a filing naming no primary document still falls back to it. - loadFilingHtml refuses an unsafe name with a message saying so. Fixes #395 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdP6Nwc3vwqfC3vq8urgqT
…read a failure as empty The S-1 and golden fixture sources called fetch() directly with `process.env.SEC_USER_AGENT ?? <own default>`. That skipped the queue's rate and in-flight caps, the cluster limiter and the 429 cooldown, and the ?? kept an empty SEC_USER_AGENT, so EDGAR got no User-Agent at all. The S-1 feeds also swallowed errors with .catch(() => ""), so a blocked run reported no candidates. - edgarFixtureFetch.ts fetches text or bytes through SecFetchTask: queued, SecUserAgent (trimmed, with the compiled-in fallback), uncached, so the golden audit still sees live bytes. A failure or missing body throws. - The S-1 feeds no longer swallow errors; a filer skipped during enrichment is logged with the reason. The hand-rolled sleeps are gone, since the queue paces requests. - startSecJobQueue registers and starts the fetch queue. bootstrapSecRuntime calls it, and so does scripts/checkFixtures.ts, which has no CLI boot; without it FetchUrlTask would build a default queue without any of this. - rawFetch.test.ts fails on any bare fetch() under src/ or scripts/, and on any read of SEC_USER_AGENT outside Constants.ts. Fixes #393 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdP6Nwc3vwqfC3vq8urgqT
sec-base was the generic Workglow CLI (task, model, mcp, workflow, agent, web) carrying a curated list of sec's tasks. It booted sec's whole runtime before commander parsed, so --help and --version died unconfigured, and it was the one binary that consumed libs' runWorkglowCli and the one the smoke check could not cover. sec's own commands are the surface; the generic task runner over them earned none of that. Removed with it: the bin entry and its build scripts, dist/libs-cli.js from the required tarball contents, the registerSecTasks list (nothing else read it), and the plain format "cik" field widget, which only task run's schema-driven forms rendered. sec's commands use "sec:cik". BREAKING CHANGE: the sec-base binary is gone. Use the sec commands. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdP6Nwc3vwqfC3vq8urgqT
`check-fixtures` reported 41 of 43 golden fixtures as remote-changed. The filings were byte-identical: sec.gov's CDN (Akamai Bot Manager; the response carries x-akamai-transformed and _abck / bm_sz cookies) now splices an empty sensor <script> with a root-relative random path into every HTML response, just before </body> (or </BODY>). EDGAR refuses active content in a submission, so a script there is never the filing. stripCdnInjection removes exactly that tag, matched as served and only before the closing body tag, through a latin1 round trip that keeps every other byte. The audit applies it before hashing, so both verify and download compare against what EDGAR stores. Removing it reproduces the manifest digest for all 41; the other 2 are served untouched. The pipeline is unaffected: the HTML parser already drops <script>. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdP6Nwc3vwqfC3vq8urgqT
- sec fetch fixtures --list wrote its rows to stdout from inside the run, under the live frame. They are now a `listed` output the command prints after the run. - The console-write guard counts process.stdout/stderr.write too, and the fixture loggers default to reportDiagnostic instead of stderr; the output-dir line goes through the injected log like every other. - The S-1 fixture source passes the filer-written primaryDocument through safePrimaryDocName and percent-encodes it, as the conversion paths do. - startSecJobQueue throws when a different queue already holds the name: that is FetchUrlTask's default queue, created by a fetch that ran first, and every request would keep going through it uncapped. - An unknown SEC form is reported once per process rather than once per row, so it cannot fill the diagnostics buffer ahead of real failures. - Smaller: the SecFetchAccessionDocTask comment says where a refused name actually fails, sanitizePrimaryDoc drops two checks URL_UNSAFE already makes, two tests follow the T | undefined convention, and the smoke-bins report no longer prints nullnull for a failed spawn. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FdP6Nwc3vwqfC3vq8urgqT
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #388, #389, #390, #391, #393, #394 and #395, removes the
sec-basebinary (#386 closed as not planned), and makescheck-fixturespass against live EDGAR again. One commit per area, plus one for review fixes.Breaking:
sec-baseremoved (212afbf)sec-basewas the generic Workglow CLI with a curated list of sec's tasks. It booted sec's whole runtime before parsing arguments, so--helpdied unconfigured (#386).binentry and its build scripts;src/libs-cli.ts;dist/libs-cli.jsfrom the required tarball contents;registerSecTaskslist, which nothing else read;format: "cik"field widget, which onlytask run's forms rendered. sec's commands usesec:cik.refactor!:with aBREAKING CHANGEfooter, so the release bump reflects the lostbinentry.smoke-bins: now covers the package's only binary,dist/sec.js.#388: console writes under the live progress frame (
64011bf)The Ink frame erases itself by line count. A
console.*write from inside a run shifts that count, so the tail of the message gets overwritten.src/util/diagnostics.tsaddsreportDiagnostic/reportDryRun, ported from embarc-data. Messages are held while a run is open and printed once it settles, capped at 200 lines with both ends kept. Outside a run they go straight toconsole.warn.runWorkflowCliwraps every run in it.console.*call outsidesrc/cli/is converted (43 under task/storage/sec, plus the ones insrc/config):reportDiagnostic.reportDryRun.updateProgress.console.errorbefore a rethrow is removed.sec setupprints its "Wrote …" lines itself, from the task's output, after the run.cik_nameswriter reports one total of collapsed duplicate CIKs instead of a line per 30,000-row slice.consoleDiagnostics.test.tspins the console-write count per file over every tree exceptsrc/cli/, exact in both directions. The only allowed write is the helper's own output.runWorkflow.diagnostics.test.tssends a failing CIK throughfetchAndStoreSubmissioninsiderunWorkflowCli. It checks that nothing reaches the console while the frame is up and that the CIK's message appears after it closes. It fails ifrunWorkflowClistops deferring (checked by reverting the wrapper).#389 and #390: one gate list, pinned Bun (
3bb86ef)scripts/releaseChecks.tsis the single list of checks, and bothrelease-checksand CI run it: format-check → lint → typecheck → build → prepack-check → smoke-bins → test. Under GitHub Actions each gate gets its own log group, and a failure is an annotation naming the gate.smoke-bins. Starts the built binary with noSEC_*environment, no.env.localand an empty working directory, and requires--helpand--versionto exit 0.prepack-check. Now refuses anysrc/path in the tarball, not justmock_data(checked by adding one source file tofiles).packageManager: "bun@1.4.3", andsetup-bunreads it withbun-version-file: package.jsoninstead of installinglatest.toolchainPins.test.tsfails ifengines.bun,@types/bun, the workflow or the gate list disagree with the pin, or if a gate is restated as its own workflow step.bun-latest.yml. Runs the same gates weekly on the newest Bun and blocks nothing..claude/CLAUDE.mdname the pinned version and the gate list.#391: coverage test timeout (
ff15b40)"Loses no content on committed fixtures" is now one
it.eachcase per fixture.#394: XBRL
value_numericfloat error (5b0d2e1)0.07at scale 2 gave7.000000000000001,0.57at scale −2 gave0.005699999999999999, and empty or whitespace text gave0. Across 357,150 two-decimal (value, scale) pairs, 36,873 were off before the fix and 0 are off after it.normalizeFactValuefolds the scale into the literal's exponent and parses once, so the result is exactlyNumber(text + "e" + scale)."", whitespace,0x10andInfinityare nownull.parseXbrlCore.test.tspins the first failing values, the full sweep, the sign, an existing exponent, and the null cases.#395: unsafe
primary_docspliced into the sec.gov URL (00e90dd)sanitizePrimaryDocalso refuses?,#,%and control characters (in a URL those mean something other than a filename);safePrimaryDocNameis its predicate form.SecFetchAccessionDocTaskvalidatesfileNameininputToUrl/inputToFileNameand percent-encodes the segment, so a refused name throws aTaskErrorbefore anything is queued.conversionCandidatesdrops an unsafe name, andconversionFetchFileNamereturnsundefinedfor a primary-document form. The task then fetches nothing and reports the filing..txt, which never uses the name..txt.sec read --fetch.loadFilingHtmlrefuses an unsafe name with a message saying so.../../x,a?banda#bat the sanitizer, the task constructor,inputToUrl, the conversion loader (asserting nocontext.own, i.e. no fetch task created) andloadFilingHtml.#393: fixture commands calling
fetch()directly (787113f)SecFetchTaskvia a newedgarFixtureFetch.ts. That gives them the queue's caps, the cluster limiter, the 429 retry andSecUserAgent, which trims an empty value and falls back to the compiled-in default.SecFetchTaskhas no output cache, so the golden audit still sees live bytes."". A filer skipped during enrichment is logged with the reason. The hand-rolled sleeps are gone, since the queue paces requests.startSecJobQueue()registers and starts the fetch queue.bootstrapSecRuntimecalls it, and so doesscripts/checkFixtures.ts, which has no CLI boot. Without it,FetchUrlTaskwould build a default queue with none of sec's limits. The script's rate limiter is in-process, since there is no database to share one through.rawFetch.test.tsfails on any barefetch(undersrc/orscripts/, and on any read ofSEC_USER_AGENToutsideConstants.ts.check-fixtures: 41 of 43 reported remote-changed (ba97ffc)<script>with a root-relative random path just before</body>(or</BODY>). The response carriesx-akamai-transformedand Akamai Bot Manager's_abck/bm_szcookies.stripCdnInjectionremoves exactly that tag, matched as served and only before the closing body tag. It works through a latin1 round trip that keeps every other byte. The audit applies it before hashing in both verify and download modes.bun run check-fixturesagainst live EDGAR now reports ok=43, failed=0.<script>.Review fixes (
759cdbc)sec fetch fixtures --list. Its rows used to be written to stdout from inside the run, under the live frame. They are now alistedoutput that the command prints after the run.consoleDiagnostics.test.tsalso countsprocess.stdout.write/process.stderr.write. The fixture loggers default toreportDiagnostic, and the output-dir line goes through the injectedlog.primaryDocumentgoes throughsafePrimaryDocNameand is percent-encoded, as on the conversion paths.startSecJobQueuethrows when a different queue already holds the name (FetchUrlTask's default queue, created by a fetch that ran first), instead of silently leaving it in place.SecFetchAccessionDocTaskcomment now says where a refused name actually fails;sanitizePrimaryDocthatURL_UNSAFEalready covers are gone;T | undefinedconvention;smoke-binsreport no longer printsnullnullwhen a spawn fails.Still open, by design for now:
--json, and console child processes.Both are pinned by tests, so changing either is a behaviour decision, left for a follow-up.
Testing
bun run release-checkspasses locally on the current head, every gate, including the full suite: 1585 passed, 21 skipped.src/verify/coverage.test.ts"loses no content on committed fixtures" times out at 30 s under full-suite load #391's five consecutive loaded full-suite runs were not repeated.🤖 Generated with Claude Code
https://claude.ai/code/session_01FdP6Nwc3vwqfC3vq8urgqT