FPDP Development Progress Report
1. Snapshot
Verification date: September 26, 2026
Evidence: repository routes, controllers, services, repositories, migrations, views, gateway plugins, tests, and deployment configuration—not planning documents alone. The test suite was re-run on the same date (see Section 8).
FPDP is past its core MVP. Since the September 21 report, visitor commerce flows are connected (public product checkout, wallet top-up, manual payment confirmation), a paid LLM + RAG visitor chatbot is live, the owner dashboard has panels for nearly every domain, and federation speaks ActivityPub (WebFinger, actor, inbox/outbox, inbound and outbound federated posts). Payment refunds, cancellation, and reconciliation are now available too. The next focus is validation against real sandboxes/servers, auditing sensitive actions, security settings, and operational hardening.
Repository snapshot:
- 59 MySQL migrations (0001–0059);
- 58 test scripts, all passing (see Section 8);
- REST APIs for identity, profiles, posts, timeline, external feeds, payments, personal online shop (including visitor checkout and digital assets), analytics, federation, LLM config, RAG, chatbot, wallet, wall comments, themes, and media upload;
- real UI: home, unified timeline (local + federated + promoted products), profile, post, shop, product page, "coretan" wall, YouTube page, payment thank-you page, and 12 owner dashboard pages;
- 3 public themes: default, editorial, minimal;
- 6 payment gateways: Dummy, Paywuz, Midtrans, PayPal (built in) + iPaymu and Manual Transfer (plugins under gateways/);
- a Dockerfile and Dokploy Compose deployment — staging live and validated;
- bilingual product and technical documentation.
2. Phase status
flowchart LR
P0["Phase 0<br/>Engineering foundation"] --> P1["Phase 1<br/>Identity & personal node"]
P1 --> P2["Phase 2<br/>Local content & timeline"]
P2 --> P3["Phase 3<br/>External aggregation"]
P3 --> P4["Phase 4<br/>MVP hardening"]
P4 --> P5["Phase 5<br/>Online shop & payment"]
P5 --> P6["Phase 6<br/>Federation"]
P6 --> P7["Phase 7<br/>Federated commerce"]
P7 --> P8["Phase 8+<br/>Ecosystem, AI, scale"]
classDef done fill:#e3efe9,stroke:#185f48,color:#17211b;
classDef partial fill:#f2e8d6,stroke:#93631e,color:#17211b;
classDef planned fill:#f1e3e1,stroke:#a13d37,color:#17211b;
class P0,P1,P2,P3 done;
class P4,P5,P6,P7,P8 partial;
| Workstream | Status | Summary |
|---|---|---|
| Engineering foundation | Done | PSR-4, router, PDO, migration runner, config, JSON envelope, exception mapping (now with exception logging), CI |
| Identity & profile | Done for MVP | Register/login/logout/me, hashed tokens, rate limiting, profile visibility, Google OAuth visitors |
| Local content | Done for MVP | CRUD, draft/publish, visibility, soft delete, media upload, lightbox, canonical URLs, cursor timeline, dashboard post list and delete |
| External aggregation | Done for MVP | RSS/Atom/Custom API, YouTube feeds, LinkedIn Organizations, anti-SSRF HTTP client, sync worker, dedup |
| Operations & hardening | Partial | CI, audit trail live for sensitive actions, centralized owner-only access, validated Dokploy staging; backup/restore exercise and retention not done |
| Personal online shop | Done for MVP | The node owner's own shop: products, orders, public visitor checkout, shipping address, digital assets + download, promoted products |
| Payment | Mostly done | 6 gateways, gateway plugin system, active-gateway selection, per-environment sandbox/live, manual confirmation, owner cancel and refund, reconciliation; real sandbox testing and settlement ledger not done |
| Federation | Mostly done | ActivityPub (WebFinger, actor, inbox/outbox, followers/following), federated posts and products, federation worker, federation dashboard; cross-server interop testing not formally documented |
| Dashboard | Mostly done | 12 live owner pages; standalone Analytics panel and security settings missing |
| AI | Mostly done | LLM providers (OpenAI, Anthropic, OpenRouter), describe-image, RAG + FAQ, paid visitor chatbot via wallet, conversation history |
| Advertising | Not started | Strategy document only |
3. What is available
3.1 Platform and baseline security
- Front controller and router support static routes, segment parameters, and canonical /@handle.
- The home page (/) renders the owner's personal digital home live, falling back to a placeholder for fresh installs or non-public profiles.
- Config reads defaults, .env, and native container environment variables.
- Passwords are hashed; bearer tokens are hashed and revocable.
- Rate limiting for register/login, wall comments, LLM calls, and RAG.
- The external HTTP client blocks private/internal targets and limits redirects, timeouts, and response size. Fixed September 26, 2026: redirects used to be followed automatically by PHP without re-validation, so an outside server could bounce a request (including actor fetches triggered by the inbox without any login) to an internal or cloud-metadata address. Every redirect hop is now validated, IPv6 (AAAA) answers are checked too with a legitimate-DNS64 exception, all of 127/8, 0/8, CGNAT 100.64/10, fc00::/7, fe80::/10, and IPv4-mapped addresses are blocked, and the final URL is reported correctly.
- Uncaught exceptions are now logged instead of only returning a generic 500.
- scripts/rotate-app-key.php re-encrypts gateway credentials and OAuth tokens transactionally.
- GitHub Actions runs syntax checks and the test suite.
3.2 Identity, profile, and visitors
- Registration creates the node, owner user, and profile in one flow.
- Login, logout, /api/v1/me, public profile, profile update, and a dashboard-editable About Me page.
- Profile visibility: PUBLIC, UNLISTED, PRIVATE.
- Google OAuth visitors with signed state and hashed visitor tokens; owners can list visitors (/api/v1/me/visitors).
3.3 Local content, media, and interaction
- Create/read/update/soft-delete posts; draft/publish; visibility and ownership enforcement.
- Up to 10 media items per post; direct upload (POST /api/v1/me/media) with server-side MIME detection, no SVG, and FILE limited to PDF.
- The post editor embeds YouTube, TikTok, and Instagram videos; bare URLs in timeline excerpts are auto-linked.
- Click-to-zoom lightbox for post images and product photos.
- "Coretan" wall (guestbook): visitors post comments; the owner replies and deletes.
3.4 External content
- RSS, Atom, Custom API, and official YouTube feed connectors with privacy-enhanced embeds.
- LinkedIn Organizations: OAuth, Organization discovery, encrypted tokens, post fetch, disconnect.
- Feed-source management, sync trigger, sync worker, deduplication, stats.
- The unified timeline merges local posts, external content, sanitized federated posts, and promoted products.
> Correction: the Facebook Pages integration and the Instagram litescrap connector listed in the previous report have been removed from the repository (commit 629c807) and are no longer available.
3.5 CV/Resume download — removed
- Removed September 27, 2026 by product decision: the /cv and /@handle/cv pages, the CV & Resume dashboard, the /api/v1/me/cv and /api/v1/profiles/{handle}/cv… APIs, paid CV access, and the "Download CV" step on the thank-you page. Digital products in the shop are the way to sell files.
- The cv_documents/cv_access_grants tables and old files in storage/cv/ are not deleted automatically (they hold data; see Section 7). Old payments with purpose cv_access still show in the Payments dashboard labelled "fitur lama" and no longer trigger anything.
3.6 Personal online shop and payment
Scope: an online shop owned by the website owner (one seller per node, not a multi-seller marketplace). Visitors can buy directly on the node, and product posts spread to the fediverse. Federated commerce — buyers on other nodes/the fediverse ordering products — is a key differentiator of the platform and remains a goal.
- Product CRUD, orders with immutable snapshots, status lifecycle.
- Public visitor checkout (POST /api/v1/profiles/{handle}/orders) with buyer identity and shipping address.
- Product digital assets: owner upload, buyer download after payment.
- Promoted products (is_promoted) appear in the timeline and are federated.
- PaymentGatewayInterface, factory, Dummy/Paywuz/Midtrans/PayPal adapters, and a gateway plugin system (gateways/*/gateway.json) with iPaymu and Manual Transfer plugins.
- Per-environment (Sandbox/Live) credentials encrypted with AES-256-GCM in payment_gateway_configs; saved non-secret values are shown in Settings, secrets are not.
- Saving a gateway verifies credentials with the provider (PayPal OAuth, authenticated Midtrans request); activation rejects incomplete configuration.
- The PayPal environment is read from database config with a PAYPAL_ENVIRONMENT fallback; iPaymu follows the same environment dropdown.
- The gateway code is resolved before payment validation (products and wallet top-up).
- Digital product download fix (September 27, 2026): the file download buttons shown after payment used to fetch the file in JavaScript and save it as a blob, which does nothing on many phones and in-app browsers (WhatsApp, Instagram, Facebook). Each file now gets a signed link valid for 15 minutes (/api/v1/products/{id}/digital-assets/{kind}/file?visitor=&expires=&token=, HMAC with APP_KEY, bound to the buyer and the file kind, the purchase re-checked at download time) that the browser downloads directly, on the product page and the thank-you page. Non-ASCII filenames are sent with filename*. New DigitalDownloadTest.
- Webhook verification and duplicate-event handling.
- Payment confirmation page for product/wallet, confirmation links across all themes, and a thank-you page (/payment/thank-you).
- Manual confirmation of pending payments by the owner (/api/v1/me/payments/pending, .../{uuid}/confirm).
- Payments dashboard: balance/approximate settlement, success rate, total refunded, recent transactions, and buyer details.
- Owner cancellation (POST /api/v1/me/payments/{uuid}/cancel): cancels at the gateway when it has an API (Midtrans, Paywuz, Dummy, Manual Transfer) and still cancels locally when the gateway refuses or has none (PayPal, iPaymu), showing the provider message to the owner. The related order is closed too.
- Owner refund (POST /api/v1/me/payments/{uuid}/refund): full or partial (refunded_amount, PARTIALLY_REFUNDED/REFUNDED status, migration 0059). Gateways with no refund API (Paywuz, iPaymu) use the manual-refund option. A full refund undoes fulfillment through PaymentFulfillmentService: order set to REFUNDED, top-up taken back out of the wallet. Refunding a top-up already partly spent on chat is refused.
- Refunds made in the provider's dashboard are now applied: Midtrans refund/partial_refund webhooks move a PAID payment to REFUNDED/PARTIALLY_REFUNDED. Previously the refund notification was dropped as a duplicate, because Midtrans reuses the same transaction_id for every notification about one transaction.
- Reconciliation (scripts/reconcile-payments.php for cron, plus the "Cek status ke gateway" button / POST /api/v1/me/payments/reconcile): asks the provider for the status of PENDING payments older than 15 minutes, applies terminal statuses, and runs fulfillment. Payments cancelled/failed locally in the last 7 days but PAID at the provider are moved to PAID, fulfilled, and reported as mismatches. Exit code 1 on any error or mismatch.
- Paywuz adapter aligned with the official Merchant API v1 docs: getPaymentStatus() uses GET /transactions/{orderId} and cancelPayment() uses POST /transactions/{orderId}/cancel (previously both threw, as if the endpoints did not exist). transaction.settlement stays PENDING. Credential verification now uses GET /payment-methods and rejects a pk_live_ key saved under Sandbox, or the reverse.
3.7 AI, RAG, and chatbot
- LLMProviderInterface + factory for OpenAI, Anthropic, and OpenRouter; per-node configuration (llm_configs).
- describe-image: drafts a product description from a photo, manually triggered and rate limited.
- RAG: owners upload documents and generate/edit/delete FAQs from them.
- Visitor chatbot with a widget in every theme; owners toggle it and set pricing.
- Visitor wallet: top-up through the active gateway, owners can grant balance manually; balance pays for chat.
- Owner-facing chatbot session and message history; a pending-orders (buying interest) filter from conversations.
3.8 Federation
- Ed25519 node identity, signing keys, and RSA keys for ActivityPub HTTP Signatures.
- WebFinger (/.well-known/webfinger), actor document, /@handle/inbox, /@handle/outbox, /followers, /following.
- Follow, Accept, Reject, Undo, Block; INCOMING/OUTGOING direction with a unique pair per direction; mutual-follow status fixes.
- Follow-request approve/reject, remote-node trust state (UNKNOWN, TRUSTED, BLOCKED), capability settings.
- Inbound and outbound Create/Update/Delete for federated posts, including image attachments; federated product delivery.
- A federation worker service that actually delivers queued activities; retry, exponential backoff, dedup, timestamp window.
- Every inbox activity and unresolved Accept/Reject is logged; actor resolution is retried before an inbound Follow is dropped.
- Federation dashboard (/dashboard/federation) with a Find & follow fediverse accounts panel (September 26, 2026), four tabs:
- Search: @user@domain or a profile URL → a preview (avatar, bio, follower/following/post counts, 3 recent posts, relationship, blocked-domain warning) before following. Works with any ActivityPub server.
- Suggestions: followers not followed back and accounts whose posts reached this node, from local data with no outbound request.
- Server directory (Mastodon GET /api/v1/directory) and Hashtag (Mastodon GET /api/v1/timelines/tag/{tag}), only on Mastodon-compatible servers that expose them.
- Remote content is reduced to plain text server-side and rendered with textContent; only http(s) URLs pass through; limited to 30 requests/minute per node. Endpoints: GET /api/v1/me/federation/discover/{lookup,suggestions,directory,hashtag}.
3.9 Analytics and dashboard
- Daily visitor hashing with HMAC + APP_KEY; raw IPs are not stored.
- Profile view, post view, outbound click, and shop conversion events; dashboard analytics API.
- Live dashboard pages: Overview, Posts (editor + list), About Me, Products, Orders (buyer info and filters), Payments, Integrations, Federation, RAG, Themes, Settings (gateway, LLM, chatbot), and Coretan.
- Theme selection from the dashboard; site navigation unified into one source across all themes.
3.10 Deployment
- Web installer for VPS/shared hosting.
- PHP 8.3 + Apache Docker image; .well-known publicly allowed in the vhost.
- Dokploy Compose: app + MySQL 8.4 + federation worker, health check, persistent volumes, automatic migrations, owner bootstrap.
- Production secret validation and web-installer lock.
4. Partially complete
4.1 Production validation
- Dokploy staging was validated (September 19, 2026), but no CI job runs all migrations against a disposable MySQL 8.
- Ubuntu VPS kit (September 27, 2026): deploy/ubuntu/ has an Nginx site (with /.well-known/ reaching PHP — the old guide's dotfile rule blocked WebFinger, so nobody could follow a VPS node), PHP upload limits, a cron file for the three background jobs plus a nightly backup, and backup.sh/restore.sh; scripts/check-requirements.php --http verifies a server. The guide now targets Ubuntu 24.04 (the old php8.2-* packages don't exist there). The same PHP limits are in the Docker image, and the Dokploy Compose gained a scheduler service.
- A restore has not yet been practised on a real server; restore.sh exists but a recovery exercise is still to be done.
- sodium is not needed: encryption uses openssl (the earlier item asking to verify it was wrong).
4.2 Payment production
- Paywuz, Midtrans, PayPal, and iPaymu are tested with a fake HTTP requester, not against real provider sandboxes. Sandbox testing needs the owner's sandbox credentials and has to be run by the owner.
- The Payments dashboard balance is still an approximation from the payments table (net of refunds); there is no settlement/payout ledger or gateway-fee accounting (the fee column is never filled).
- A partial refund made in the Midtrans dashboard only moves the status to PARTIALLY_REFUNDED; the amount is not recorded, since the notification does not reliably carry it.
- PayPal and iPaymu have no cancel API; cancellation is local only and the provider's payment page stays open until it expires (reconciliation catches it if it is paid anyway).
4.3 Dashboard and settings
- Analytics page done (September 27, 2026) at /dashboard/analytics: 7/30/90-day ranges, four stat tiles (visits, page views, outbound clicks, shop orders) with the change vs the previous period, a daily line chart (crosshair tooltip, keyboard-operable, responsive down to phone width) with a table view, top pages with post/product titles, where visitors came from (referrer domain only), and outbound links clicked. API GET /api/v1/me/analytics?days=.
- Correction: visits used to be recorded only from JSON API endpoints, not from the HTML pages visitors actually open, so analytics were nearly always empty; outbound clicks were never sent either. The front controller now records every successful GET of a public page (after the response is sent), excluding bots, crawlers, fediverse link previews, prefetches, the dashboard, and the API; outbound clicks go through sendBeacon to POST /api/v1/track/outbound-click. Migration 0063 adds referrer_host.
- Language settings done (September 27, 2026): every public page (home, profile, about me, guestbook, timeline, post, YouTube, shop, product, checkout, thank-you page, chatbot widget, navigation, About FPDP) is available in Indonesian and English in all three themes. The owner picks the enabled languages and the default one in Settings (GET/PATCH /api/v1/me/locale-settings, migration 0062). A visitor's language comes from ?lang= (remembered in the fpdp_lang cookie), then that cookie, then the browser's Accept-Language, then the node default — always limited to the enabled languages. There is a language switcher in the navigation, <html lang>, and hreflang tags. Catalogs live in app/Lang/{id,en}/; a missing key falls back to the other language, a broken catalog file is skipped. Owner-written content is never translated. The owner dashboard is not translated (out of scope).
- Node settings still missing: custom CSS/layout and general node config (themes and languages are available).
- Session management done (September 27, 2026): an Account security section in Settings lists the devices signed in (device label, IP truncated to /24 or /48, sign-in and last-active times, this-device marker), logs out one device or all others, and changes the password (previously impossible) — the current password is required, 12–128 characters, and every other device is logged out. All of it is audited without passwords or full IPs; tokens expired/revoked for over 30 days are deleted at login. Migration 0061.
- 2FA done (September 27, 2026), optional for the owner account: TOTP (RFC 6238), which works with Google Authenticator, Microsoft Authenticator, Authy, 1Password, Bitwarden, Aegis, and the like. It is turned on under Settings → Keamanan akun: scan the QR code (drawn in the browser by the MIT qrcode-generator library bundled in the repo, so the secret goes to no third-party service) or type the key by hand, then confirm with a first code. 10 one-time recovery codes follow (shown once, stored as password hashes). With 2FA on, a correct password only yields a 5-minute challenge (5 tries at most). A session is issued only after a 6-digit code or a recovery code is verified. Each code works once. The secret is encrypted with APP_KEY (covered by rotate-app-key.php). Turning 2FA on logs other devices out; turning it off needs the password and a code. Every step is audited without secrets or codes. Emergency path if both the phone and the recovery codes are lost: php scripts/disable-2fa.php --email=… on the server. check-requirements.php now checks clock sync (NTP). Migration 0064. None of the 15 dashboard login forms needed changing: owner-login-mfa.js adds the code step.
4.4 Federation production
- No formal record yet of interop testing between two FPDP instances or another ActivityPub server on separate domains, although many recent fixes came from real-world testing.
- Replay protection does not use a nonce cache.
- Stored capabilities do not yet enforce feature access.
- Older follow data may need an INCOMING/OUTGOING direction backfill.
- app/Services/Federation/Federaltest.php looks like a scratch file inside the service namespace and should be reviewed.
4.5 External integrations
- LinkedIn is not validated against the production Community Management API.
- The YouTube embed renderer on timeline/profile is still limited; playlist normalization is unsupported.
4.6 Authorization, audit, and analytics hardening
- Correction: the previous report said the audit trail already covered auth, posts, profile, and wall comments. In fact AuditService was never wired into routes.php, so no audit event was ever recorded in production. Fixed on September 26, 2026.
- Done (September 26, 2026): an owner-only access model is enforced centrally in AuthService: only an ACTIVE user with role OWNER can log in or use a token; a suspended account or any other role is refused immediately even with a still-valid token, and the refusal is recorded as access.denied. There is no admin role, per the product decision (one website = one owner).
- Done (September 26, 2026): the audit trail now records user.registered, user.login, user.login_failed, access.denied, payment.confirmed_manually, payment.cancelled, payment.refunded, payment.reconciled (including from cron), payment_gateway.configured (key names only, no values), payment_gateway.activated, llm.configured (no API key), wallet.granted, federation.trust_changed, federation.blocked, and federation.capabilities_updated, alongside the existing post/profile/wall-comment events. Payment entries do not copy buyer personal data. A failed audit write does not undo the action; the error goes to the server log.
- Fixed (September 27, 2026), ahead of federated commerce:
- GET /api/v1/orders/{id} was open without login and leaked the buyer's name, email, and shipping address. Only the selling node's owner or the visitor who placed the order can read it now; anyone else gets 401/404.
- GET /api/v1/products/{id} and the public product list returned digital_asset_url (the link buyers pay for), even for PRIVATE products. That URL is now handed out only by the purchase-gated download endpoint; PRIVATE/inactive products are 404 to the public.
- The federation inbox accepted unsigned activities and never checked that the key belonged to actor, so accounts you follow could be impersonated. Signatures are now required, must cover (request-target) host date digest, the Digest must match, Date must be within 5 minutes, and the key must belong to actor; the key is re-fetched once to handle rotation.
- There is no dashboard page to browse the full audit trail yet (Overview only shows recent activity), and no retention for audit_events.
- Owner access to buyer personal data (Payments/Orders pages) is not logged per view yet.
- ~~The public outbound-click endpoint has no dedicated rate limit.~~ Done: 60 per 10 minutes per IP.
- ~~analytics_events has no retention/cleanup job.~~ Done: scripts/prune-analytics.php (daily cron on a VPS, the scheduler service on Dokploy) deletes events older than ANALYTICS_RETENTION_DAYS (default 400, minimum 30).
5. Not started
- Advertising marketplace: ad slots, pricing, booking, approval, delivery windows.
- Per-post paywall (per-post pricing and entitlement).
- OAuth connectors for Facebook, Instagram, X, Threads, TikTok, and Shopee.
- AI cost controls/usage reporting beyond rate limits (per-node spend ceiling).
- Production plugin/adapter marketplace (a local gateway plugin system exists).
- ~~Federated commerce — native cross-node orders~~ Done September 27, 2026 (node-to-node orders): the owner of another FPDP node orders from their own dashboard (Belanja, /dashboard/purchases) with an HTTP-signed fpdp:OrderRequest; the seller node prices it, charges its own gateway, and returns the payment page; retries with the same client reference never double-order; status and 15-minute digital download links are read back with signed GETs (on open, on refresh, and by scripts/sync-purchases.php every 15 minutes); the seller's Orders page labels the buyer actor. Migration 0065, FEDERATION-CONCEPT §11b. Remaining: push status updates instead of pull, retention of purchase copies, and a two-domain run on real servers. Also working since September 27, 2026 under the link checkout model: products go out as a Note + fpdp:product block (price, currency, type, checkout URL), are withdrawn with Delete when archived/private/unpromoted, and receiving FPDP nodes render them as product cards with a "Buy on {seller}" link to the seller's checkout. Receivers validate the block (checkout URL must be https on the seller's host) and only accept Create/Update/Delete from the object's author. See FEDERATION-CONCEPT §11a. There is no stock yet.
- Multi-node administration, shared/object storage, horizontal scaling.
- Formal contribution policy (CONTRIBUTING.md). The license is set: Apache-2.0 (LICENSE, NOTICE).
6. Next priorities
flowchart LR
A["1. Fix failing<br/>tests"] --> B["2. Payment sandbox<br/>+ refund & reconciliation"]
B --> C["3. Sensitive audit<br/>+ RBAC"]
C --> D["4. Security &<br/>node settings"]
D --> E["5. Formal federation<br/>interop"]
E --> G["6. Federated<br/>commerce"]
G --> F["7. Ads, post paywall,<br/>social OAuth"]
1. ~~Fix the PostEndpointsTest fixture (slug column) and make RSA-based tests independent of local OpenSSL configuration.~~ Done (September 26, 2026) — 47/47 tests pass.
2. Partly done (September 26, 2026): owner refund/cancel, applying refunds from webhooks, a reconciliation job, and aligning the Paywuz adapter with its official docs are in place. Remaining: testing Paywuz, Midtrans, PayPal, and iPaymu against real sandboxes (needs the owner's credentials) and scheduling the reconciliation cron in Dokploy.
3. ~~Add credential changes, gateway activation, manual confirmation, wallet grants, and remote-node trust to the audit trail; add RBAC middleware.~~ Done (September 26, 2026) — the audit trail is wired in and covers every sensitive action; owner-only dashboard access is enforced centrally (no admin role).
4. ~~Implement 2FA~~ Done (September 27, 2026), along with session management, password change, language settings, and the Analytics page.
5. Document a two-domain federation interop test and add a nonce cache.
6. ~~Start Phase 7 Federated Commerce~~ Mostly done (September 27, 2026): product representation, link checkout, and node-to-node orders with payment on the seller node and status synced back. Remaining: pushed status updates, purchase-copy retention, a two-domain interop run.
7. Add an analytics retention job and an outbound-click rate limit.
8. Only then continue with advertising, per-post paywall, additional social OAuth, and ecosystem work.
7. Risks and operational decisions
- Old CV data: the CV feature is gone, but the cv_documents/cv_access_grants tables (holding buyer identity) and files in storage/cv/ remain on servers that used it. Per ISO/IEC 27001:2022 A.8.10 (information deletion), both can now be archived and then deleted with scripts/archive-cv-data.php (verified archive, deletion only while the archive still matches, audited); migration 0066 only renames the tables to archived_cv_* without deleting data. See DEPLOYMENT-GUIDE §4.9a. Test ArchiveCvDataTest.
- Deployment assumes one owner and one app replica per node; do not scale before migration locking and shared storage exist.
- MySQL and storage/ (CVs, media, digital assets, RAG documents) must be restored from a consistent recovery point.
- Code rollback does not reverse forward migrations.
- APP_KEY protects OAuth state, analytics HMAC, gateway credentials, and OAuth tokens; rotation must use scripts/rotate-app-key.php.
- Manual payment confirmation grants access/fulfillment without provider proof; it is now in the audit trail (who, when, which payment), but the transfer evidence itself stays outside the system.
- The chatbot uses the owner's LLM API key; without a spend ceiling, abuse can run up provider bills.
- Buyer personal data (name, email, phone, shipping address) is now stored in orders and payment metadata (and in old CV grants, if that table is still there). In line with ISO/IEC 27001:2022 controls (A.5.34 Privacy and protection of PII, A.8.10 Information deletion, A.8.15 Logging), the following must be defined: a processing basis and privacy notice for buyers, retention and deletion periods, restricted dashboard access, and auditing of access to buyer data.
- Payment and federation must be tested against real remote systems before being called production-ready.
8. Latest validation
Run on September 26, 2026 in the development workspace (PHP 8.5.8 CLI, Windows), using the same loop as CI (php tests/*Test.php):
- 58 of 58 tests pass, including the new FederatedOrderTest (seller: a signed order priced by the seller, retry returns the same order and its open payment page, unsigned/foreign-key/swapped-body/stale requests refused, reference squatting, private products, address, return URL, self-order, blocked domain, status readable only by the buyer, download links bound to order and expiry, audit without address; buyer: shop list, a signed request checked against this node's key, the seller's figures kept, signed status pull, only https downloads, unreachable seller kept and re-sent with the same reference, a refusal leaves nothing behind, malformed answers ignored, owner API); the Belanja page was checked in a headless browser at 1280px and 375px; including the new TotpTest (RFC 6238 test vectors, ±30-second drift, replay, otpauth URI) and TwoFactorTest (setup and confirm, encrypted secret, other devices logged out, a login challenge that is not a session token, wrong code, replay, 5-try limit, expiry, one-time recovery codes, regeneration, disable, audit without secrets); the login code dialog was checked in a headless browser; including the new AnalyticsReportTest (public-page classification, bot vs in-app browser filtering, host-only referrer, when a visit is recorded, the report with previous period and titles, endpoints, rate limit, retention); the Analytics page was checked in a headless browser at 1280px and 375px (hover, keyboard, no horizontal scroll); including the new I18nTest (language resolution order, limiting to enabled languages, cookie, fallback, JS catalog, validation, owner API, switcher). All 12 public views were rendered in 3 themes × 2 languages with no raw keys, and the ?lang=/cookie/Accept-Language flow was tried over a real HTTP server; including the new SessionManagementTest (session list, logging out one/all other devices, another user's session unreachable, password change and its rules, audit without secrets), with no OPENSSL_CONF needed. New HttpClientSsrfTest (non-public addresses, redirect to cloud metadata, legitimate cross-host redirects, redirect loops, 303) and FediverseDiscoveryTest (preview, HTML/javascript: sanitized, cross-host outbox paging not fetched, suggestions, directory, hashtag, input validation). Directory and hashtag were also tried live against mastodon.social. The new OwnerAccessAuditTest checks owner-only access (suspended accounts and other roles are refused even with a valid token), auditing of failed logins, gateway and LLM configuration, confirmation, refund, and cancellation, that secrets and buyer personal data never reach the audit trail, and that login still works when the audit table is broken. The new PaymentRefundCancelReconcileTest covers cancellation, full/partial/manual refunds, refunding an already-spent top-up, a Midtrans refund webhook after settlement, reconciliation (provider status, minimum age, gateways without a status API, errors, mismatches), and the dashboard summary. PaywuzGatewayTest used to silently send real requests to api.paywuz.id; it now uses a fake requester.
- Fixed in this round:
- PostEndpointsTest: the SQLite fixture now has the posts.slug column, and the canonical URL assertion follows the /posts/{id}-{slug} format introduced in commit 24ab57b.
- FederationInboxTest, FederatedPostIngestionTest, MutualFollowTest: previously failed because Windows/XAMPP PHP could not find openssl.cnf, so openssl_pkey_new() failed (error:80000003). The same issue would also break node federation key generation on Windows deployments. NodeKeyService::createRsaKeyPair() now tries OpenSSL's default config first, then falls back to app/Services/Federation/openssl-fallback.cnf; all three tests use the same helper.
- Docker build was not run in this workspace; the staging deployment was confirmed by the project owner.
9. References
- Roadmap
- WBS
- PRD
- ERD
- API Contract and OpenAPI
- Mockup