Skip to content

feat(publish): answer the marketplace scan gate instead of failing on it - #185

Open
A1igator wants to merge 2 commits into
mainfrom
A1igator/scan-ack-flow
Open

feat(publish): answer the marketplace scan gate instead of failing on it#185
A1igator wants to merge 2 commits into
mainfrom
A1igator/scan-ack-flow

Conversation

@A1igator

@A1igator A1igator commented Aug 18, 2026

Copy link
Copy Markdown
Contributor

Scope

The CLI now answers the server-side ingest scan gate, so a warn-tier refusal becomes a decision the operator can act on instead of an opaque exit-4 write failure. src/lib/scan-gate.ts parses the two 422 codes and merges server findings with the local scan's, deduped by detector and offset. src/lib/consent.ts maps them into the exit-3 flow publish and edit already share: scan_blocked is terminal with no acknowledgement path, scan_needs_ack renders once and re-runs the identical content with the ack token on an explicit yes, exactly once. Risk is the wire body: scanAck only ships on a retry the server itself asked for, so a deployment predating the gate never sees the key on a body its strictObject schema would reject.

Mode semantics are the local ones (src/lib/consent.ts acksServerWarnings): review and auto stop on a server warn, full-auto acks it, and ackServerWarnings: false never acks whatever the mode says, which is the seam the unattended observer lane needs later.

Advisory findings on a success response reach the receipt as scan and the human summary as informational lines; findings travel as data, so a detector this release predates renders faithfully and the server stays authoritative under version skew.

Testing

  • pnpm lint, pnpm typecheck, pnpm format:check, pnpm build: clean.
  • pnpm test: 2173 passed, 10 skipped, 69 files.
  • New: 16 unit tests in src/lib/scan-gate.test.ts (envelope parsing, token-never-on-block, unknown detector and tier, merge/dedupe by offset and by value, malformed-finding drop), 11 command tests in publish.test.ts and 5 in edit.test.ts against a stubbed gate (needs_ack hold, token re-run with identical body, full-auto auto-ack, auto stop, never-ack override, unknown-detector render, blocked in every mode, advisory on success, no-token hold, no loop on a second needs_ack), and one renderer test in output.test.ts for the source marker.
  • Every gate test runs against a stubbed response. No live server.

Definition of Done

  • RELEASING.md gains a server-coupled-releases section: the tenjin repo's SCAN_WARN_MODE=enforce flip waits for this release to propagate, which is the whole reason the flag exists.
  • docs/safety-model.md states the client/server split: the local scan runs first so a block-tier secret never leaves the machine; the server is authoritative for the marketplace.
  • Changeset added (minor).

Notes

  • Server sibling: BackTrackCo/tenjin#723 (A1igator/ingest-scan-gate), built against head 5fbed15f. This lands after it merges.
  • The openapi.fixture.json contract pin is deliberately untouched: scanAck is not in the deployed contract yet. Regenerate it with the tenjin deploy, not here.
  • The skill-drift (mirror is in sync) check does not fire here (no skills/ change). Where it does run on a fresh branch it is red for a shared cause owned by feat(cli): tenjin pay and tenjin discover, with the bazaarPay toggle #151, and is not chased.

Refs #182, tenjin#723.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@A1igator A1igator added the priority: critical Blocks other PRs; merge-priority label Aug 18, 2026

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@A1igator A1igator added priority: high High priority and removed priority: critical Blocks other PRs; merge-priority labels Aug 21, 2026
A1igator and others added 2 commits August 21, 2026 23:49
The tenjin backend now runs the publish scan server-side in its shared write
path (tenjin#723), so a publisher not running this CLI is gated too. Its
refusals reached `publish` and `edit` as opaque post-consent write failures,
which is the wrong shape for a decision the operator can act on.

Both writing commands map the gate's two codes into the consent flow they
already have: `scan_blocked` is a hard exit-3 failure with the redacted
findings and no acknowledgement path, and `scan_needs_ack` merges the server's
findings with the local scan's, deduped by detector and offset, renders them
once, and re-runs the identical content carrying the ack token on an explicit
yes. Mode semantics are the local ones: review and auto stop, full-auto acks,
and `ackServerWarnings: false` never acks whatever the mode says.

Findings travel as data, so a detector this release predates renders faithfully
and the server stays authoritative under version skew. Advisory findings on a
success response reach the receipt and the human summary as information.

Refs #182, tenjin#723.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A raw NUL byte in the source made git treat scan-gate.ts as binary, so its
diff never rendered for review. Same key, written as an escape.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@A1igator
A1igator force-pushed the A1igator/scan-ack-flow branch from b498ef0 to b7edb15 Compare August 22, 2026 03:53

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your trial has ended. Reactivate Greptile to resume code reviews.

@A1igator

Copy link
Copy Markdown
Contributor Author

Rebased onto main at b7edb15. The branch was CONFLICTING after #150, #181 and #199 landed. It is MERGEABLE now.

Two files conflicted, and both were semantic rather than textual, so recording how each resolved:

src/lib/posts-api.tsmain widened searchId to string | string[] (the multi-search-id change) and derives a local through toWireSearchId(normalizeSearchIds(...)). This branch adds scanAck. The two do not interact: I took main's widened type and its normalized local on all three hunks, and kept scanAck beside them.

src/commands/publish.tsmain replaced the single searchInfo object with a searches array plus a closeLine helper. That supersedes this branch's version of the same code outright, so I dropped my side of the hunk rather than merging the two shapes. What this branch actually contributes there is two lines, both kept: ...scanNoteLines(result.scan) in the human output and ...(result.scan !== undefined ? { scan: scanReceipt(result.scan) } : {}) in the data payload. The deps interface took both new optional fields, searchIdLabel from main and ackServerWarnings from here.

No searchInfo reference survives in the file, so nothing is left pointing at the shape main removed.

What ran at this head: pnpm install --frozen-lockfile, pnpm typecheck, and pnpm run check (lint, format, full suite) which exits 0 with 76 test files passed and 2414 tests passed, 10 skipped. The notify-registry-pin: FAIL and skill-resync push-error lines in that output are fixture text from tests that assert loud failure, not real failures.

Still REVIEW_REQUIRED.

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

Labels

priority: high High priority

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant