Repository navigation
Conversation
ae2aecb to
5e7f579
Compare
9a4828b to
33b1193
Compare
5e7f579 to
86b2ad7
Compare
33b1193 to
4a62a77
Compare
Coherence4 spec problems · 0 failing chokepoints · 2 spec gaps · 0 guard failures 44 components, 7 bullets (5 invariants, 2 requirements), 4 problems Spec problems (spec --check)
Chokepoints: none declared. Spec gaps: 2 entrances with outside or unknown trust and no traced control, and 10 more held by the adoption baseline
Close one with |
Node SDK compliance v2Commit: da59e27 Capture v0 — acceptance✅ 28 passed / ❌ 3 non-passing / Drill-down: run artifacts → ❌ server_snapshot_payload_false — failed_assertionExpected native data result Code: Failed step: acceptance/public/evaluate-flags.feature:868 the public snapshot outcomes should have these semantics: ❌ server_snapshot_payload_zero — failed_assertionExpected native data result Code: Failed step: acceptance/public/evaluate-flags.feature:868 the public snapshot outcomes should have these semantics: ❌ server_snapshot_payload_null — failed_assertionExpected native data result Code: Failed step: acceptance/public/evaluate-flags.feature:868 the public snapshot outcomes should have these semantics: Harness exit: 1; report validation exit: 1. Capture v1 — acceptance✅ 28 passed / ❌ 3 non-passing / Drill-down: run artifacts → ❌ server_snapshot_payload_false — failed_assertionExpected native data result Code: Failed step: acceptance/public/evaluate-flags.feature:868 the public snapshot outcomes should have these semantics: ❌ server_snapshot_payload_zero — failed_assertionExpected native data result Code: Failed step: acceptance/public/evaluate-flags.feature:868 the public snapshot outcomes should have these semantics: ❌ server_snapshot_payload_null — failed_assertionExpected native data result Code: Failed step: acceptance/public/evaluate-flags.feature:868 the public snapshot outcomes should have these semantics: Harness exit: 1; report validation exit: 1. Capture v0 — migration✅ 55 passed / ❌ 1 non-passing / Drill-down: run artifacts → ❌ migration/yaml-parity-v1/remote-flags-v1.feature::Omitted GeoIP settings default to enabled in flag requests — failed_assertionFlags field differs Code: Failed step: migration/yaml-parity-v1/remote-flags-v1.feature:134 the first flags request field "geoip_disable" should equal JSON false Operation: Field: Expected: false Actual value: true Harness exit: 1; report validation exit: 1. Capture v1 — migration✅ 120 passed / ❌ 1 non-passing / Drill-down: run artifacts → ❌ migration/yaml-parity-v1/remote-flags-v1.feature::Omitted GeoIP settings default to enabled in flag requests — failed_assertionFlags field differs Code: Failed step: migration/yaml-parity-v1/remote-flags-v1.feature:134 the first flags request field "geoip_disable" should equal JSON false Operation: Field: Expected: false Actual value: true Harness exit: 1; report validation exit: 1. |
86b2ad7 to
db5ac2d
Compare
4a62a77 to
95be057
Compare
A code send with templateData sends template: { key, hash }. When PostHog
answers template_outdated, the SDK uploads the content by key and sends again
once with the same idempotency key. Moves the local template arm of
PostHog#5371, with its content hash module.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
95be057 to
da59e27
Compare
db5ac2d to
8384b8c
Compare
Part of the code-first rebuild of posthog-node's email API; replaces parts of PostHog#5371.
Problem
A team that keeps its email templates in code wants PostHog to render them with per-send data, without uploading by hand on every deploy. This PR adds the local template: a code send with
templateDatasends a content hash, and the SDK uploads the template by key only when PostHog has an older version.Stacks on #6. Needs the server's template upload by key and
template: { key, hash }arm.Impact
Same as the stack: Grow Workflows, billed
workflows_emailsfrom API sends.Merge Danger
Caution
🔴 Merge risk: high · one-way door (merge-risk rubric)
PUT messaging_templates/by_key/{key}/) from code, guarded byif_code_managedandif_not_superseded. The content hashv1becomes a format the server and posthog-python must keep computing the same way.Changes
templateData): the SDK copies the body, hashessubject,htmlandtextwith the content hash (v1), and sendstemplate: { key, hash }withmessage_data.409 template_outdatedit uploads by key (PUT messaging_templates/by_key/{key}/withif_code_managed: trueandif_not_superseded: true) and sends once more with the same idempotency key.409 template_edited_in_posthogreturns as the error; a secondtemplate_outdatedreturns as the error.email-content-hash.tsand its tests, held back from feat(node): send transactional email from code with emails.send #5.capture_engagementsenttruewhen absent and feat(node): send transactional email from code with emails.send #5's one field set per form, which gainstemplateDatahere.Evidence
posthog.emails.send()on a real client withFakeEmailsApi, which keeps templates by key and hash; the content hash through its public function.capture_engagement: true; the upload then the resend with one idempotency key;template_edited_in_posthognot retried; a secondtemplate_outdatedreturned; options changed during hashing change nothing.4a62a77a: nodevitest(1329 passed), edge (1329 passed),tsc,oxlint,oxfmt, build, references committed; corevitest.Live run, done on 2026-10-26 at
da59e276, against Django at feat(messaging): send each email once per idempotency key posthog#578c4c5ff05d2f(slice 09, with 05e20426aa1e5, 066730e409b4band 08d0eeb308f962below it) and cdp-api at feat(cdp): replay a keyed send after its workflow or default sender changes posthog#5801a43bdda327, on a local stack with a dev mail catcher. Every send namesfrom: Support <support@example.com>andreplyTo: help@example.com:409 template_outdated, upload201, send202, one idempotency key on all threeReply-To: help@example.com, variables filled, a text part derived from the html202409, upload200, send202, one keyCross-version (hash
v1against the server's hash): covered by shared test vectors in the content-hash tests, and live: the server's upload returned the samecontent_hashthe SDK sent, so a matching key sent in one request.Production shape: gap until read-only production access is available.
Decisions
No load-bearing decisions.
Involvement: carries the PostHog#5371 review rounds; a fresh HEAD read and a final read on the restack (both clean), live run captured.
Release info Sub-libraries affected
Libraries affected
Checklist
If releasing new changes
pnpm changesetto generate a changeset file🤖 Agent context
Autonomy: Human-driven (agent-assisted)
claude-opus-5-5) in Claude Code, as build lane js2 of map Map: send code-managed email templates with one PostHog call posthog#461, directed by Michael. The code comes from the reviewed stack feat(node): send transactional email with the flat SDK contract PostHog/posthog-js#5312, feat(node): send email content from code, uploading local templates by hash PostHog/posthog-js#5371, feat(node): send cc, bcc, headers, tags and attachments with a code send PostHog/posthog-js#5373 and feat(node): read an email's status with emails.get PostHog/posthog-js#5372 (closed; fork copies feat(node): send transactional email with the flat SDK contract #1–feat(node): read an email's status with emails.get #4), restacked code-first. The equality with each reviewed head is checked by diff and listed in the lane ledger.🤖 Generated with Claude Code