Skip to content

IngestAdvSnapshotTask.folder still decouples the snapshot label from the archive it reads — and no caller passes it #367

Description

@sroussey

532f61d closed the traversal half of this input. It did not close the mislabeling half, which is
the failure advArchiveFolder's own JSDoc says the per-snapshot folder exists to prevent:

a single folder holds the cumulative archive and every month at once — and an ingest reading it
stamps thirteen years of filings with the period it was asked for.

Both inputs below pass every guard the commit added. "2026-06" is a valid period, so
advArchiveFolder(input.snapshot) is satisfied (IngestAdvSnapshotTask.ts:186); adv/cumulative
resolves inside SEC_RAW_DATA_FOLDER, so the prefix check is satisfied (:188-194). Nothing
checks that the folder is this snapshot's folder.

Reproduction (passes today)

Against TestingDI, with a real 2026-06 archive and a real cumulative archive on disk:

await new IngestAdvSnapshotTask().run({ snapshot: "2026-06" });
expect((await advisers.query({ snapshot: "2026-06" })) ?? []).toHaveLength(1);   // the real June row

await new IngestAdvSnapshotTask().run({
  snapshot: "2026-06",
  folder: advArchiveFolder("2011-2024"),        // both guards satisfied
});

const landed = (await advisers.query({ snapshot: "2026-06" })) ?? [];
expect(landed).toHaveLength(1);                       // PASSES
expect(landed[0].crd_number).toBe("330001");          // PASSES — the cumulative archive's adviser
expect(landed[0].date_submitted).toBe("2013-04-01");  // PASSES — stamped snapshot "2026-06"

The real June-2026 row is destroyed by the deleteSearch that runs first (:210-213), and a 2013
filing takes its place under the June-2026 label. sec show advisers --min-aum then filters on a
2013 AUM, and sec status's newest-snapshot reading reports 2013 data as June 2026 — the exact
consequence the commit message describes fixing, one level up from where it was fixed.

folder has no caller

$ grep -rn "IngestAdvSnapshotTask" src --include=*.ts | grep -v '\.test\.'
src/cli/sync/registerSecSyncLeaves.ts:209:        new IngestAdvSnapshotTask({
src/cli/groups/bootstrap.ts:289:              new IngestAdvSnapshotTask({
$ grep -rn "folder:" src/cli src/task/adv --include=*.ts | grep -v '\.test\.'
src/task/adv/IngestAdvSnapshotTask.ts:166:      folder: Type.Optional(Type.String()),

Both call sites pass only snapshot. The input exists solely as surface for sec-base task run,
the web console form and the MCP tool surface — which is precisely the threat model the commit
message names.

Fix

Delete the input, or set const dir = resolve(root, advArchiveFolder(input.snapshot)) and drop
folder from the schema. Either makes the label and the bytes one decision again, and removes the
prefix check along with the input it was written for. If it must stay for a manual re-extract,
require it to equal advArchiveFolder(snapshot) and say so in the error.

Two smaller notes on the same guard:

  • The check is resolve()-based, so it does not resolve symlinks. A symlink under
    SEC_RAW_DATA_FOLDER passes.
  • input.folder ?? snapshotFolder does not catch "", so folder: "" resolves to the raw-data
    root itself and throws rather than defaulting — a confusing message for an empty string.

Also: 0.2.0 on npm predates this fix

@workglow/sec@0.2.0 was published 2026-09-08T22:27Z; 532f61d landed 2026-09-09 19:31. The
published build still joins SEC_RAW_DATA_FOLDER with a completely unvalidated folder and skips
advArchiveFolder's check on snapshot when one is supplied. See the release issue.

Found during the 2026-09-14 review. Snapshot: workglow-dev/prdanalysis/grades/2026-09-14/sec-detailed.md §4.1.

Activity

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

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions