e9e6300cfc
gates / gates (push) Successful in 4m35s
Read phase passes (Page 1360018983863273, CREATE_CONTENT/MODERATE/ANALYZE, page token PAGE expires_at 0, three insights metrics alive on v26.0). Write test: removal check accepts Meta's code-10 'Object does not exist' (fixed without a row, 4 tests); run 1 evidence kept. R-914 READY, R-915 narrowed to Live mode.
76 lines
7.5 KiB
Markdown
76 lines
7.5 KiB
Markdown
# SPIKE — can Claude Code run the Felhom.eu Facebook Page? (2026-10-08)
|
||
|
||
**Verdict: yes.** The system-user key is valid and never expires; it reaches the Felhom.eu Page with every task the
|
||
skill needs; a scheduled text post and a scheduled photo were created, read back byte-equal (Hungarian accents) and
|
||
deleted, each removal proven by a failed GET. **Open:** posts made while the app is in development mode are seen
|
||
only by people with a role on the app (read, not measured) — public posting needs the app switched to Live (R-915).
|
||
|
||
Baseline `felhom.eu` @ `fe0dc0d03f`. Probe: `scripts/facebook/fb_probe.py` (stdlib; tests `test_fb_probe.py`).
|
||
Evidence: `facebook-page-api-2026-10-08/` — `read` run at the top level, `write-test/` the passing write run,
|
||
`write-test-run1-strict-check/` the first write run (see „Removal proof"). Graph API **v26.0** accepted, no fallback.
|
||
Grades: **measured** = this run; **read** = Meta's documentation, not run.
|
||
|
||
**Architecture: no document in `documentation/architecture/` covers marketing or social media** (checked `00`–`12`).
|
||
Correct — the Page is a business tool, not part of the product. Decision home: `CONTEXT.md` (2026-10-08,
|
||
„Facebook Page"); rows R-914, R-915.
|
||
|
||
**Timeline.** First `read` (twice): `/me/accounts` empty — the app was assigned to the robot, the Page was not.
|
||
The operator assigned the Page (Business settings → Pages → Felhom.eu → assign `felhom-cc`); the **same key**,
|
||
not regenerated, then saw the Page. A system-user token is not pinned to a Page list (measured).
|
||
|
||
## Findings
|
||
|
||
| Question | Answer | Grade | Evidence |
|
||
|---|---|---|---|
|
||
| Key valid? type? | `is_valid: true`, `SYSTEM_USER`, app `2273465403490709` (`felhom.eu`) | measured | `A1-debug-token.json` |
|
||
| Does it expire? | `expires_at: 0`, `data_access_expires_at: 0` — never | measured | `A1` |
|
||
| Scopes | `read_insights, pages_show_list, business_management, pages_read_engagement, pages_read_user_content, pages_manage_posts, pages_manage_engagement, public_profile`; granular scopes carry no `target_ids` | measured | `A1` |
|
||
| Robot | id `122094150717513084` (app-scoped), name `felhom-cc`; Business Suite shows it as `61595392523486` (business-scoped) — the same robot | measured | `B1-me.json` |
|
||
| Page reached | exactly one: `Felhom.eu`, **Graph ID `1360018983863273`** (the browser profile address shows `61595336666018`) | measured | `B2-me-accounts.json` |
|
||
| Page tasks | `CREATE_CONTENT, MODERATE, MESSAGING, ADVERTISE, ANALYZE, MANAGE_LEADS, VIEW_MONETIZATION_INSIGHTS` | measured | `B2` |
|
||
| Page token | derived from `/me/accounts?fields=…,access_token`; `type: PAGE`, `expires_at: 0`; a fresh value on each derivation (199 / 201 chars) | measured | `B3-debug-page-token.json` |
|
||
| Page fields | name, link, category `Information Technology Company`, about, website, `followers_count 0`, `fan_count 0` | measured | `C1-page.json` |
|
||
| Feed | 1 post (profile picture update) | measured | `C2-feed.json` |
|
||
| Insights (v26.0, `period=day`) | `page_post_engagements`, `page_follows`, `page_media_view` all answer (values 0) — none deprecated | measured | `C3-insights-*.json` |
|
||
| Scheduled text post — D | `POST /{page}/feed` `message`, `published=false`, `scheduled_publish_time=now+7d` → id `{page}_{post}`; read back `is_published: false`, `scheduled_publish_time` equal, **message hex equal** | measured | `write-test/D1`, `D2`, `D9-verdict.json` |
|
||
| Scheduled photo — E | `POST /{page}/photos` multipart `source` (`website/assets/logo.png`), `caption`, `published=false`, `scheduled_publish_time` → **only `id` (a photo id), no `post_id`**; photo `name` read back **hex equal**; DELETE on the photo id | measured | `write-test/E1`, `E3`, `E9-verdict.json` |
|
||
| Is App Review / Live needed to post? | **No refusal in development mode**: both writes answered HTTP 200 — no (#200), (#10) or (#3) | measured | `D1`, `E1` |
|
||
| Dev-mode posts visible to the public? | **No**: „Any data generated while an app is in Development mode, such as test posts, can only be seen by role users"; visible to all once the app is Live | read | [app modes](https://developers.facebook.com/docs/development/build-and-test/app-modes) |
|
||
| App Review for our own Page? | Standard Access is automatic and covers users/assets with a role on the app; Advanced Access (review + business verification) is for others' assets | read | [access levels](https://developers.facebook.com/docs/graph-api/overview/access-levels) |
|
||
| What does Live need? | display name, contact e-mail, **Terms of Service URL**, app icon, category, app purpose (each „required to switch your app to Live mode"); Privacy Policy URL and data-deletion URL listed beside them | read | [basic settings](https://developers.facebook.com/docs/development/create-an-app/app-dashboard/basic-settings) |
|
||
| Headers | `facebook-api-version: v26.0`; `x-business-use-case-usage` keyed by business `4713349378884589`, type `business_integration_system_user_platform_endpoints`; `x-app-usage` | measured | `F1-headers.json` |
|
||
|
||
## Removal proof — a deleted post answers code 10, not 100
|
||
|
||
| Object | DELETE | GET after DELETE (quoted) |
|
||
|---|---|---|
|
||
| text post `1360018983863273_122096071749511222` (run 1) | `{"success": true}` | (#10) „Object does not exist, cannot be loaded due to missing permission or reviewable feature, or does not support this operation…" `fbtrace_id AVitleYH5GQggv9QstbpU4Q` |
|
||
| text post `1360018983863273_122096072277511222` (run 2) | `{"success": true}` | same (#10) text, `fbtrace_id Aiogseplh0f5BKjQ8tqEzpR` |
|
||
| photo `122096072325511222` (run 2) | `{"success": true}` | (#100, subcode 33) „Unsupported get request. Object with ID '122096072325511222' does not exist…" `fbtrace_id AREMCG8p12jhITdpE8zqsPp` |
|
||
|
||
Run 1 stopped (exit 7) because the probe expected code 100. Code 10 also means a missing permission, so it is
|
||
counted as removal only because the **same Page token read the same post with HTTP 200 seconds before the DELETE**
|
||
(`D2-readback.json`). `gone_error()` now accepts code 100, or code 10 with „Object does not exist" (tests in
|
||
`test_fb_probe.py`). A different-channel control — Meta Business Suite → Planner showing no scheduled post — is the
|
||
operator's look, not run here.
|
||
|
||
## Gaps (for the skill)
|
||
|
||
- **The photo's publish state was not read**: the photo object has no `is_published`, and no `post_id` came back, so
|
||
„scheduled, not public" is unproven for photos. Its `created_time` was the create time (16:36 UTC), while the text
|
||
post's `created_time` was its scheduled time. The skill should read scheduled photos back through the Page's
|
||
scheduled-post listing before trusting the photo path.
|
||
- Is the system user a „role user" for the dev-mode visibility rule? Not stated in the docs read; measured only after Live.
|
||
|
||
## What the skill needs
|
||
|
||
- Base `https://graph.facebook.com/v26.0/`; token `FACEBOOK_API` via `load_key()` (R-453 strip + asserts), sent as
|
||
`Authorization: Bearer`; never in a logged URL.
|
||
- Page token: `GET /me/accounts?fields=id,name,tasks,access_token`, pick `name == "Felhom.eu"` (id `1360018983863273`),
|
||
memory only, re-derive per run.
|
||
- Text: `POST /{page}/feed` form `message`, `published=false`, `scheduled_publish_time=<unix>` → `id`.
|
||
- Photo: `POST /{page}/photos` multipart `source`, `caption`, `published=false`, `scheduled_publish_time` → `id` (photo).
|
||
- Delete: `DELETE /{id}` → `{"success": true}`; prove with `GET /{id}` → error per `gone_error()`.
|
||
- Stats: `GET /{page}/insights?metric=<m>&period=day` for the three metrics above.
|
||
- Every response through `redact()`; HTTP 200 with an `error` body counts as failure.
|