Skip to content

INSTAGRAM-002E: source-only canonical social-post content and payload hash - #682

Open
rusty-sj wants to merge 1 commit into
mainfrom
rusty-sj/issue-92-social-post-content
Open

INSTAGRAM-002E: source-only canonical social-post content and payload hash#682
rusty-sj wants to merge 1 commit into
mainfrom
rusty-sj/issue-92-social-post-content

Conversation

@rusty-sj

Copy link
Copy Markdown
Contributor

Outcome

Add the source-only contract that defines a social post's officer-editable content and derives the one canonical hash that binds it — the caller-side payloadHash computation the INSTAGRAM-002A reducer and INSTAGRAM-002B plan builder assume. Nothing new runs; no content is stored.

Scope

  • Issue: INSTAGRAM-002 — Add an approval-gated social-post state and audit model #92 (INSTAGRAM-002). Independent of INSTAGRAM-002B: source-only social-post persistence-plan builder and Rules boundary #681 (002B) — branches off main.
  • What changed:
    • functions/socialPostContent.js (new) — validateSocialPostContent(candidate) reads the exact revision-1 record { contentSchemaVersion, caption, mediaReference, altText, scheduledAtEpochSeconds, displayTimeZone, disclosureRequired } and returns a frozen canonical projection or the fixed reason invalid_content, non-throwing in the §8.7 idiom. computeSocialPostPayloadHash(candidate) validates the same record and, only on success, returns a lowercase SHA-256 hex digest over the fields in fixed order with length-framed, domain-separated encoding (mprc-social-post-payload-sha256 / mprc.social-post-payload.v1); on an invalid record it throws SocialPostContentError so a bad record can never mint a hash. The digest satisfies the reducer's opaque-identifier shape, so it drops straight into a §8.7 command as payloadHash and a record as approvedHash.
    • Bounds: caption 1..2200 chars, TAB/LF allowed, other C0/DEL/C1 controls rejected; mediaReference an opaque bounded url-safe handle (never a URL); altText 1..1000, one line; scheduledAtEpochSeconds a safe integer in [0, 4102444800] (UTC, canonical); displayTimeZone an IANA-style zone name or UTC, presentational only; disclosureRequired boolean.
    • functions/socialPostContent.test.js (new) — 43 tests: acceptance (incl. TAB/LF in caption, UTC and three-segment zones, inclusive schedule bounds), ~25 rejection cases, Proxy/accessor rejection without trap or getter invocation, hash shape (^[0-9a-f]{64}$ and the reducer's opaque-id pattern), determinism, per-field digest sensitivity, a field-boundary collision guard, throw-on-invalid with no input echo, and a source-boundary battery (requires only node:crypto + node:util; not imported by index.js; no clock/randomness/network/provider surface; names no concrete provider; digest is domain-separated).
    • SYSTEM_DESIGN.md §8.24.
  • What did not change: no content store, no Firestore schema or Rules, no callable, no index.js wiring, no migration. This is the one contract in the 002 set that legitimately holds a caption and alt text; it still carries no URL, recipient, member, or provider vocabulary. It computes no schedule-to-wall-clock conversion, reads no clock, and hashes only a validated record.

Officer handoff

  • Officer impact: None. No officer-visible behavior, page, task, permission, or data movement changes.
  • Officer documentation: None — source-only contract; the canonical-content editor and its bounds/escaping policy remain NOT AVAILABLE YET and are owner decisions under INSTAGRAM-002 — Add an approval-gated social-post state and audit model #92.
  • Approving role: n/a for this slice.
  • Preview or redacted screenshot: n/a — no UI.
  • Screenshot checked at: n/a.
  • Plain-language undo plan: revert the commit; nothing is deployed, wired, or reachable.
  • Deployment evidence: none. Source, tests, and (pending) merge only.

Proof by surface

  • Source changed: yes.
  • Tests passed: yes.
    • npm --prefix functions run lint — clean.
    • npm --prefix functions run test:run -- --runInBand — 70 suites / 7612 pass (2 unrelated emulator suites skipped).
    • npm run test:rules — 5 suites / 418 pass (unchanged; this slice touches no Rules).
  • Code merged: not yet.
  • Website published: not relevant.
  • Netlify intended commit verified: not relevant.
  • runmprc.com verified: not relevant.
  • Firebase deployed: not relevant.
  • Outside provider configured: not relevant.
  • Outside provider verified: not relevant.
  • Production behavior verified: not relevant.

Safety review

  • No secrets, recovery codes, private member data, or payment data are included.
  • Planned behavior is marked NOT AVAILABLE YET.
  • Skipped checks or deployments are reported as incomplete, not green/live.
  • Page/data/access/deployment diagrams were updated when their flow changed — n/a; no flow changed.
  • A backup officer can follow the affected guide without a terminal, or the specialist-only step is explicit — n/a; no officer guide is affected.

Owner decisions still needed (do not block this slice)

  • Caption max length, whether the disclosure flag is mandatory for a post, and the accepted media/schedule policy — needed before the canonical-content editor (a later slice), not before this contract.

… hash

functions/socialPostContent.js — the caller-side computation the §8.7 reducer
and the INSTAGRAM-002B plan builder assume ("payloadHash is computed by the
caller"). Defines the officer-editable content record { contentSchemaVersion,
caption, mediaReference, altText, scheduledAtEpochSeconds, displayTimeZone,
disclosureRequired } with conservative bounds (caption <= 2200 with TAB/LF;
one-line alt text <= 1000; opaque media handle, never a URL; UTC epoch second in
[0, 4102444800]; IANA-style zone or UTC for display only). validateSocialPost-
Content is non-throwing in the §8.7 idiom (frozen projection or 'invalid_content').
computeSocialPostPayloadHash validates then returns a lowercase SHA-256 hex over
the fields in fixed order with length-framed, domain-separated encoding, and
throws SocialPostContentError on an invalid record so junk can never mint a
hash. The digest satisfies the reducer's opaque-identifier shape.

This is the one contract in the 002 set that legitimately holds caption/alt
text; it still carries no URL, recipient, member, or provider vocabulary.
Requires only node:crypto + node:util, imported by no runtime path, defines no
Rules or endpoint, reads no clock/randomness/network.

functions/socialPostContent.test.js — 43 tests: acceptance, ~25 rejection cases,
Proxy/accessor rejection, hash shape/determinism/per-field sensitivity/length-
framing, throw-on-invalid, source boundary.

SYSTEM_DESIGN.md §8.24.

Verification: npm --prefix functions run lint clean; test:run 70 suites / 7612
pass; npm run test:rules 5 suites / 418 pass.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GPs7CQHK2h8KqhgKKsXevv
@netlify

netlify Bot commented Aug 30, 2026

Copy link
Copy Markdown

Deploy Preview for luminous-fox-7c393f ready!

Name Link
🔨 Latest commit 520cbf2
🔍 Latest deploy log https://app.netlify.com/projects/luminous-fox-7c393f/deploys/6a945d4314bcfc0008b05238
😎 Deploy Preview https://deploy-preview-682--luminous-fox-7c393f.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
🤖 Make changes Run an agent on this branch

To edit notification comments on pull requests, go to your Netlify project configuration.

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