Skip to content

feat(scripts): sync fetcher entry scripts to Paramify + associate to evidence sets#3

Merged
21tmccauley merged 7 commits into
mainfrom
feat/paramify-scripts-sync
Jul 23, 2026
Merged

feat(scripts): sync fetcher entry scripts to Paramify + associate to evidence sets#3
21tmccauley merged 7 commits into
mainfrom
feat/paramify-scripts-sync

Conversation

@21tmccauley

Copy link
Copy Markdown
Collaborator

What

Adds a paramify scripts sync command (backed by a new uploaders/paramify_scripts/ uploader) that uses the new v0.6.0 /scripts API to push each fetcher's entry file to Paramify and CONNECT it to that fetcher's evidence set — so the tenant shows how each piece of evidence was generated.

Provisioning step, separate from paramify upload: run it when fetchers/** change, not on every collection.

Design (why it's a reconcile, not a create)

The /scripts API gives us {id, name, description, code} with no stable external key (only a server UUID) and no server-side versioning. So the tool reconciles the tenant to the repo, GitOps-style:

  • identity — a marker in the script description:
    paramify-fetcher: <fetcher name>
    version: <fetcher.yaml version>
    sha256: <sha256 of entry file>
    
    Indexed client-side from GET /scripts (there is no server-side name/marker filter).
  • versioning — the fetcher.yaml version is the update signal; git is history-of-record; the app holds "current".
  • drift guard — the sha256 catches a code edit that forgot to bump the version: warn + skip by default, --force to push.
  • association — get-or-create the evidence set by reference_id (same identity paramify_evidence uses, honoring the same overrides config), then POST /evidence/{id}/associate CONNECT SCRIPT. Idempotent-tolerant; --reassociate re-drives every fetcher to heal a partial failure.

Decisions baked in (per discussion)

  • Entry file onlyfetcher.py/fetcher.sh; shared modules ignored.
  • Script display name = evidence_set.name.
  • Only SCRIPT associations are automated; solution-capability / control-implementation / validator linkage stays Paramify-side.

Actions per fetcher

create (new) · update (version bumped) · drift (code changed, version didn't — warn/skip, --force) · noop.

Files

  • uploaders/paramify_scripts/{uploader.py,uploader.yaml,README.md}
  • framework/api.pyscripts_sync + scripts_sync_preflight facade (mirrors the evidence uploader loader)
  • framework/cli.pyparamify scripts sync (--dry-run/--force/--reassociate/--json)
  • tests/test_scripts_uploader.py — 8 tests (marker roundtrip, action decisions, drift gate, association-on-change, --reassociate, https guard, per-fetcher error isolation)
  • CLAUDE.md — documents the command + uploader

Testing

  • Full suite green: 91 passed (83 existing + 8 new).
  • Offline --dry-run plans all 107 fetchers as would_create (discovery + entry-file read + marker planning exercised without touching the API).

⚠️ Before merge

Not yet run against a live tenant (needs PARAMIFY_UPLOAD_API_TOKEN). Please run paramify scripts sync --dry-run (read-only) against your tenant to confirm the plan, then a real sync on a couple of fetchers. One assumption to verify on that first real call: that POST /evidence/{id}/associate CONNECT is idempotent / tolerant of an already-connected script (the API has no pre-check and GET /evidence/{id} doesn't expose associations, so the client treats an already/exists/connected 400/409 as success).

Follow-ups (out of scope)

  • Extract a shared Paramify HTTP client from paramify_evidence + paramify_scripts (kept self-contained here to avoid destabilizing the tested evidence uploader).
  • Validator association now has an API — separate decision.

🤖 Generated with Claude Code

Tate McCauley and others added 2 commits July 9, 2026 11:37
…evidence sets

Adds the paramify_scripts uploader and a `paramify scripts sync` command that
pushes each fetcher's entry file (fetcher.py/fetcher.sh) to the new Paramify
/scripts API and CONNECTs it to that fetcher's evidence set, so the tenant shows
how the evidence was generated.

The /scripts API has no stable external key and no server-side versioning, so
the tool reconciles the tenant to the repo (GitOps):
- identity: a marker in the script `description` (`paramify-fetcher: <name>`),
  indexed client-side from GET /scripts (no server name filter exists).
- versioning: the fetcher.yaml `version` is the update signal; a sha256 of the
  entry file guards undeclared drift (warn+skip by default, --force to push).
- association: get-or-create the evidence set by reference_id (shared identity
  with paramify_evidence), then POST /evidence/{id}/associate CONNECT SCRIPT.
  Only SCRIPT is automated; solcap/control/validator linkage stays manual.

Separate from evidence upload (provisioning, run on fetcher change). Shared
modules are intentionally ignored — only the entry file is pushed.

- uploaders/paramify_scripts/{uploader.py,uploader.yaml,README.md}
- framework/api.py: scripts_sync + scripts_sync_preflight facade
- framework/cli.py: `paramify scripts sync` (--dry-run/--force/--reassociate/--json)
- tests/test_scripts_uploader.py: marker roundtrip, create/update/drift/noop,
  drift gate, association, --reassociate, https guard, error isolation
- CLAUDE.md: document the command + uploader

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Surfaces `paramify scripts sync` in the TUI by folding it into the (renamed)
Paramify tab alongside evidence upload — the two share token, base_url,
overrides config, and the event-stream shape, so they share the log/worker/
preflight machinery.

- rename tab 5 "Upload" -> "Paramify" (id unchanged)
- UploadPage: "Sync Scripts" button + dry-run/force/reassociate checkboxes,
  a ScriptsSyncEvent worker, and sync_start/sync_item/sync_complete rendering
- the Scripts action is repo-scoped: gated on scripts_sync_preflight
  (token + fetchers), independent of the run picker; dry-run runs read-only
  without a confirm, a real sync confirms first and requires the token
- rebuild() now shows EVIDENCE and SCRIPTS readiness sections side by side
- extend the CLI/TUI parity harness to know the `scripts` command group,
  symmetric with `manifest` (EXPECTED_SCRIPTS + API_TO_CLI entries)

Verified via a Textual pilot: Paramify tab composes, widgets resolve, Sync
Scripts enables on 107 fetchers with no token (dry-run path). Full suite: 91 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@21tmccauley

Copy link
Copy Markdown
Collaborator Author

Added TUI support (commit 5fe7e0b): folded scripts sync into the renamed Paramify tab (was "Upload") alongside evidence upload, since both share token/config/event-stream plumbing.

  • "Sync Scripts" button + dry-run / force / reassociate checkboxes
  • repo-scoped: enabled on token+fetchers via scripts_sync_preflight, independent of the run picker; dry-run is read-only (no confirm), a real sync confirms + requires the token
  • the CLI/TUI parity harness now understands the scripts command group (symmetric with manifest)

Verified with a Textual pilot (tab composes, widgets resolve, button enables on 107 fetchers in the no-token dry-run path). Full suite: 91 passed.

Tate McCauley and others added 3 commits July 9, 2026 12:23
…squat)

`paramify tui` told users to run `pip install 'paramify[tui]'`, but this project
is not published to PyPI and its distribution name is `paramify-fetchers`, not
`paramify`. The bare name resolves to an unrelated PyPI package (`paramify`),
which pip installs instead — clobbering the editable install and still leaving
textual absent. Point at the editable local install from the repo root:
`pip install -e '.[tui]'`, and warn off the PyPI form.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Resolve conflicts from main's public-release + Tier-1 TUI design work:

- CLAUDE.md: accept main's deletion (removed as an internal-only file for
  the public release; the scripts command stays documented in
  uploaders/paramify_scripts/README.md).
- tests/test_cli.py: union EXPECTED_TOP — keep main's `ksi`/`doctor` and
  the PR's `scripts` command.
- framework/tui/screens/upload.py: combine the scripts-sync feature with
  main's design vocabulary — border titles, palette.OK/WARN/FAIL roles,
  status pills, empty-state toggles, banner-on-error. `_disable_actions`/
  `_restore_actions` cover the new Sync Scripts button; scripts-sync log
  styling harmonized to palette so both halves of the screen render alike.

Full suite green: 231 passed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…der design doc

The scripts-sync feature was documented only in its own uploader README;
close the gaps left after main's CLAUDE.md removal:

- docs/uploader_design.md — new canonical uploader design doc covering both
  built uploaders and the shared evidence-set identity model. Resolves the
  dangling `docs/uploader_design.md` reference in three places (both
  uploaders' modules + the fetcher schema), which pointed at a file that
  never existed.
- README.md — a "Show how evidence is generated" section under Collect/upload,
  a paramify_scripts entry in the file tree, and docs-table rows for the
  scripts README and the new design doc (the command was previously
  undiscoverable from the top-level README).
- CHANGELOG.md — an Unreleased "Added" entry for `paramify scripts sync` and
  the new design doc.

Docs only; full suite still green (231 passed).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Tate McCauley and others added 2 commits July 23, 2026 09:52
`scripts_sync_cmd` reused the name `e` (bound earlier by `except ... as e`)
as a loop variable in the preflight-error printer, which mypy flags as
"Assignment to variable 'e' outside except: block" [misc] and fails
`mypy framework/` in CI. Rename to `err`, matching the convention used by
the manifest command error printers.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@21tmccauley
21tmccauley merged commit c48abd4 into main Jul 23, 2026
7 checks passed
@21tmccauley
21tmccauley deleted the feat/paramify-scripts-sync branch July 23, 2026 16:17
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant