Skip to content

INSTAGRAM-002B: source-only social-post persistence-plan builder and Rules boundary - #681

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

INSTAGRAM-002B: source-only social-post persistence-plan builder and Rules boundary#681
rusty-sj wants to merge 1 commit into
mainfrom
rusty-sj/issue-92-social-post-store

Conversation

@rusty-sj

Copy link
Copy Markdown
Contributor

Outcome

Add the source-only persistence-plan builder that turns an approved-before-published social post transition into an exact Firestore write set, and add the server-only Rules boundary for the post collection. Nothing new runs yet.

Scope

  • Issue: INSTAGRAM-002 — Add an approval-gated social-post state and audit model #92 (INSTAGRAM-002). Builds on the merged INSTAGRAM-002A reducer (socialPostState.js, §8.7).
  • What changed:
    • functions/socialPostStore.js (new) — buildSocialPostPersistencePlan(input). A pure, non-throwing builder that composes classifySocialPostTransition into a deterministic plan: a socialPosts/{postId} update carrying the reducer projection plus revision := expectedRevision + 1, and one append-only auditEvents row under a social_post aggregate discriminator, its document id a deterministic social_post_{postId}_{nextRevision} join so a retried transaction collides on the same id instead of appending twice. Returns precondition { collection, docId, expectedRevision } for the caller's transaction to assert. Rejected verdicts (reducer_rejected) and the reducer's idempotent no-op (no_write_needed) yield zero writes; a malformed envelope yields invalid_store_input. Clock-free (auditInstant is a caller-supplied opaque token); requires only node:util and ./socialPostState.
    • functions/socialPostStore.test.js (new) — plan shape, all 13 command types → audit event types, approve/edit verdict passthrough, six reducer-rejection passthrough cases, idempotent no-op, 23 malformed-envelope cases, Proxy/accessor rejection without trap invocation, determinism and input-immutability, deep-freeze, hostile-canary non-echo, and a source-boundary battery (no clock/randomness/network/provider surface, no PII or credential vocabulary, no concrete provider name, not imported by index.js).
    • firestore.rules — server-only socialPosts/{postId} boundary (allow read, write: if false, nested records included), matching the existing memberDirectoryEntries pattern.
    • tests/firestore-rules/social-post.test.js (new) — allow/deny coverage for socialPosts/{postId}, its nested records, and the auditEvents trail across anonymous / would-be author / verified member / unverified browser admin / verified browser admin.
    • SYSTEM_DESIGN.md §8.7 — addendum documenting this slice.
  • What did not change: no callable endpoint, no index.js wiring, no transaction is opened, no Instagram/provider adapter, no scheduling clock, no officer UI, no migration. The audit trail reuses the existing server-only auditEvents collection. The selfApprovalAllowed policy default is unchanged and remains an owner decision (open question on INSTAGRAM-002 — Add an approval-gated social-post state and audit model #92).

Officer handoff

  • Officer impact: None. No officer-visible behavior, page, task, permission, or data movement changes.
  • Officer documentation: None — this is a source-only contract with no usable officer procedure; drafting/approval remains NOT AVAILABLE YET.
  • 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 / 7629 pass (2 unrelated emulator suites skipped).
    • npm run test:rules — 6 suites / 433 pass, including the new social-post.test.js.
    • node --test tests/ci-workflow.test.js tests/test-artifact-safety.test.js tests/root-dependency-security.test.js — 72 pass.
  • Code merged: not yet.
  • Website published: not relevant.
  • Netlify intended commit verified: not relevant.
  • runmprc.com verified: not relevant.
  • Firebase deployed: not yet — the socialPosts Rules boundary ships to Firebase only through the protected release path.
  • 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 (the §8.7 lifecycle diagram is unchanged).
  • 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.

Open question for @daliu

The selfApprovalAllowed flag on a command still needs the owner policy: may the same officer author and approve a post, or must the approver differ? It does not block this slice — the builder only threads the flag through and the reducer already enforces both directions — but the callable command entry point in the next slice needs the default.

…Rules boundary

Compose the merged INSTAGRAM-002A classifySocialPostTransition reducer into a
pure, non-throwing buildSocialPostPersistencePlan(input): given a store envelope
plus the reducer's own record and command, return the deterministic Firestore
write set (socialPosts/{postId} update + one append-only auditEvents row under
the social_post aggregate) and the optimistic-concurrency precondition a
caller's transaction must assert. Rejected and idempotent-no-op verdicts yield
zero writes. No I/O, no clock (auditInstant is a caller token), node:util +
./socialPostState only, unregistered in index.js.

Add the server-only socialPosts/{postId} Rules boundary (allow read, write: if
false, nested records included) with allow/deny coverage across the anonymous /
would-be author / member / browser-admin actor matrix in
tests/firestore-rules/social-post.test.js. The audit trail reuses the existing
server-only auditEvents collection.

Document both slices in SYSTEM_DESIGN.md 8.7. Officer docs unaffected: a
source-only contract with no officer-visible behavior, task, page, permission,
or data movement.

Verification:
- npm --prefix functions run lint: clean
- npm --prefix functions run test:run: 70 suites / 7629 pass (2 emulator suites skipped)
- npm run test:rules: 6 suites / 433 pass (includes new social-post.test.js)
- node --test tests/ci-workflow.test.js tests/test-artifact-safety.test.js tests/root-dependency-security.test.js: 72 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 8a65249
🔍 Latest deploy log https://app.netlify.com/projects/luminous-fox-7c393f/deploys/6a9457ae19cbe90008072ab4
😎 Deploy Preview https://deploy-preview-681--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