Skip to content

ci(hubspot): add auth/API test receipt job - #243

Open
seanspeaks wants to merge 3 commits into
nextfrom
ci/hubspot-auth-receipt
Open

seanspeaks wants to merge 3 commits into
nextfrom
ci/hubspot-auth-receipt

Conversation

@seanspeaks

@seanspeaks seanspeaks commented Nov 9, 2026 •

Copy link
Copy Markdown
Contributor

Why

FC-B-QR Binding C item 4 is unresolved: the HubSpot live suites either skip or require a token, and there is no CI receipt with command / result / auth mode / classification / reviewer / exclusions. Strategy aimed HubSpot first. Architect's rev1 rules that a synthetic or recorded receipt can close item 4a when the field set and named checks hold. This PR ships that 4a job only. It does not implement 4b, handle a token, or claim to unblock FC-C.

What

  • GitHub Actions workflow .github/workflows/hubspot-auth-api-receipt.yml (path-filtered to the HubSpot package, receipt scripts, lockfile; also workflow_dispatch).
  • jest.receipt.config.js runs the 4a suites without MongoMemoryServer: auth-api-synthetic.test.js, oauth.test.js, definition.test.js, listContacts.test.js. Live api.test.js and auther.test.js are excluded and listed as exclusions.
  • Synthetic fixtures under packages/v1-ready/hubspot/tests/fixtures/, transcribed from official HubSpot docs (OAuth 2026-03, GET /integrations/v1/me, GET /crm/v3/objects/contacts), with GENERATOR.md + provenance.json (generator tool/command/digest, ISO-8601 retrieved-at, API-version pins). Token values are synthetic-* placeholders.
  • Tests execute the module's real auth code with only transport faked. Nock replies 200 only when outbound shape matches (method, URL, grant_type, client_id). A wrong-shape POST does not receive the success fixture (T-C4-1). Negatives: bad code / 401 / inactive token / tampered fixture / expired refresh.
  • Fail closed: nock.disableNetConnect against the pinned host list api.hubapi.com, app.hubspot.com, api.hubspot.com; job fails if any name on the closed env list / HUBSPOT_ / MATTERS_ prefixes is present and non-empty.
  • Receipt script emits JSON + a classification file whose exact bytes are synthetic (packet must be byte-equal). Uploaded as artifact hubspot-auth-api-receipt. Also written to $GITHUB_STEP_SUMMARY.
  • Freshness: API-version pin and FRESHNESS_MAX_AGE_DAYS = 90 in scripts/ci/hubspot-4a-policy.cjs, marked pending-architect (not an adopted pin). Missing, 99999, or stale bounds fail T-C4-9.

Receipt schema (local name, not fc-b.receipt.v1)

aml.hubspot.auth-api-receipt.v1 — proposed 4a artifact. Fields include: classification / classificationSha256, source/artifact pins, tests/harness/lock/config digests, command + result + CI artifact ref, auth mode (OAuth2 authorization_code), generator + freshness, reviewer (unresolved pending independent review), exclusions, policy pins (hosts, env names, max-age).

Synthetic vs authorized

Classification enum is synthetic | recorded | authorized. This job emits only synthetic. It refuses --classification authorized and will not relabel. 4b remains Sean-held.

How Sean would add a secret for a later 4b run (not in this PR)

If Architect requires 4b, Sean places a HubSpot developer test-account token into CI secrets himself. No bot creates an account or handles the token. This PR does not wire ${{ secrets.* }}.

Local commands and results

See /workspace/hubspot-ci-receipt/run-log.txt. Short form (9 Nov 2026, America/New_York):

  • node --test scripts/ci/hubspot-auth-api-receipt.test.cjs — 11 passed
  • npx jest --config jest.receipt.config.js --ci --watchman=false --forceExit — 33 passed, 0 failed, 0 skipped
  • Receipt emit: result: pass, classification bytes synthetic

Default npm test (mongo globalSetup) fails on this box: MongoMemoryServer needs libcrypto.so.1.1 (OpenSSL 3 only). That is why the 4a command uses jest.receipt.config.js.

Open questions

  • Architect may revise rev1 again. The 90-day max-age is a clearly marked placeholder.
  • Whether a synthetic receipt with unresolved reviewer binding satisfies a later packet's item 4a until Taskmaster/Architect review lands.
  • Whether npm ci (full lockfile) is acceptable in CI; a workspace-filtered install misses @aws-sdk/client-scheduler, which @friggframework/core@2.0.0-next.89 requires but does not declare.

Closes #242

Update: 19a5221 (erratum-2 receipt ref fields)

Commit 19a5221ecc791960344cf8c5282018290154de90 on top of 4c10075, 5 files, +155/−4:

  • The receipt now emits moduleRef.{table,branch,sha,apiJsSha256} from a closed map: V1 main@d50c6a9d8f6f8264aaf4e6e9447991b4074ec455 and NEXT next@9f5e5c627a0be42cd25cf28339142891e71eba07. A missing, short or unlisted ref fails closed with UNKNOWN_MODULE_REF. The workflow passes --module-ref-branch next --module-ref-sha 9f5e5c627a0be42cd25cf28339142891e71eba07.
  • source.sourceCommitSha is still the harness commit. It is not a module ref.
  • The receipt records artifact.apiJs.sha256 for the api.js Node resolves at runtime and checks it against moduleRef.apiJsSha256 (af0b0bdc937820697a9bf158a6cc4f8a9e4fa22c96c535ee5af994720e4c3c60 for NEXT). A mismatch fails 4a with E2-APIJS. A jest test also hashes the runtime-resolved api.js.
  • tests/fixtures/GENERATOR.md now says the FRESHNESS_MAX_AGE_DAYS = 90 bound is adopted (rev2 §P2-FRESHNESS) and still requires the API-version pin. The policy constants are unchanged; they were already 90 / 'adopted' at 4c10075.
  • api.js, the policy file, fixture bytes and provenance.json are untouched.

Local runs:

node --test scripts/ci/hubspot-auth-api-receipt.test.cjs   → 22/22 pass (4 failed before the code, as written test-first)
npx jest --config jest.receipt.config.js                   → 35/35 pass
Open in Web Open in Cursor 

Frigg Module Builder added 3 commits November 9, 2026 18:20
Emit a Binding C item 4a synthetic receipt for packages/v1-ready/hubspot.
Run real getToken, refreshAccessToken, addAuthHeaders, and testAuthRequest
against nock, with outbound request-shape assertions and fail-closed
network-deny plus a closed token-env list. Do not wire secrets or emit
an authorized label.
The receipt step in CI run 42673233531 failed after 33/33 tests because
the workflow passed the jest command via HUBSPOT_RECEIPT_COMMAND, which
matches the T-C4-8 HUBSPOT_ prefix. Rename to AML_RECEIPT_COMMAND, keep
the CLI --command argument, and add a regression that invokes the
emitter with the same env and argv the workflow uses.

Also align the 4a receipt with erratum rev2:
- adopt the Architect-pinned 90-day recorded freshness bound
- keep synthetic fixtures off the 90-day clock
- name T-C4-9 a-d (stale-but-pinned, fresh-but-unpinned, non-90 bound,
  API-version mismatch)
- pin deny+spy hosts to api.hubapi.com and app.hubspot.com
- treat set-but-empty provider env as present
- install a distinct spy (nock.emitter no-match) beside disableNetConnect
- exclude the unexercised hapikey / api_key path
- upload the receipt artifact only when both files exist

Local only; does not push.
… receipt; mark 90-day freshness adopted in docs

- name: Write receipt and classification bytes
env:
AML_RECEIPT_COMMAND: npx jest --config jest.receipt.config.js --ci --watchman=false --forceExit --json --outputFile=artifacts/jest-results.json

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The command field in the receipt is not the command that actually ran.

  • The test step runs in packages/v1-ready/hubspot with --outputFile="${GITHUB_WORKSPACE}/artifacts/jest-results.json".
  • AML_RECEIPT_COMMAND records --outputFile=artifacts/jest-results.json, which resolves to packages/v1-ready/hubspot/artifacts/... if someone replays it.
  • The receipt exists to record the exact command and result. Replaying the recorded command writes the JSON to a different location, and the receipt does not mention the working directory. T-C4-8 regression tests also pin this string as WORKFLOW_RECEIPT_COMMAND, so the mismatch is locked in.

Fix: make the executed command and the recorded command identical, for example by running the same string from the same directory and recording the working directory in the receipt:

env:
  AML_RECEIPT_COMMAND: npx jest --config jest.receipt.config.js --ci --watchman=false --forceExit --json --outputFile=../../../artifacts/jest-results.json

In that case, also update WORKFLOW_RECEIPT_COMMAND to match.

Suggested change
AML_RECEIPT_COMMAND: npx jest --config jest.receipt.config.js --ci --watchman=false --forceExit --json --outputFile=artifacts/jest-results.json
AML_RECEIPT_COMMAND: npx jest --config jest.receipt.config.js --ci --watchman=false --forceExit --json --outputFile=../../../artifacts/jest-results.json

Spotted by Graphite

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

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