diff --git a/.github/workflows/companion-ci.yml b/.github/workflows/companion-ci.yml new file mode 100644 index 0000000..c9ae15a --- /dev/null +++ b/.github/workflows/companion-ci.yml @@ -0,0 +1,58 @@ +name: PIXIE Companion review checks + +on: + pull_request: + paths: + - 'apps/companion/**' + - '.github/workflows/companion-ci.yml' + - 'AGENTS.md' + +permissions: + contents: read + +concurrency: + group: companion-checks-${{ github.ref }} + cancel-in-progress: true + +jobs: + companion: + name: Companion tests, typecheck and build + runs-on: ubuntu-latest + timeout-minutes: 20 + defaults: + run: + working-directory: apps/companion + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + # Requirements are enforced by behavioral tests, not text matching. + - name: Read binding project instructions + run: | + test -s ../../AGENTS.md + cat ../../AGENTS.md + - name: Enable repository-pinned pnpm + run: corepack enable + - name: Install exact lockfile dependencies + run: corepack pnpm install --frozen-lockfile + - name: Unit and integration tests + run: corepack pnpm test + - name: Type check + run: corepack pnpm run typecheck + - name: Build static Companion + run: corepack pnpm run build + - name: Confirm generated entrypoint + run: test -s tangled-site/index.html + + existing-guide: + name: Preserve existing guide + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: '22' + - run: npm run check + - run: npm run build diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9ddc5d5 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,40 @@ +# PIXIE Companion — binding development instructions + +Applies to this repository's `apps/companion` changes. Read these before editing. + +## Authority and order +1. Read `apps/companion/lib/principles.mjs`, the canonical [ecosystem architecture](https://github.com/ibloud/ibloud.github.io/blob/main/docs/ECOSYSTEM_ARCHITECTURE.md), affected source, current tests, and PR discussion. Preserve established requirements; an implementation gap is **not** permission to alter scope. +2. Identify the existing owner/source, exact defect, minimum files to change, and behavior that must not change. Reuse existing functionality before proposing new code. +3. Never reinterpret temporary implementation limitations as product requirements. If requirements conflict, report the exact source conflict before changing behavior. +4. Implement only the approved correction, without extra sign-in steps, repositories, databases, services, dependencies, directories, or unrelated UI. Preserve established CSS, accessibility, privacy, exports, and legacy data. +5. Validate with unit, integration, typecheck, production build, end-to-end user flows, and security negative tests. Green CI is not proof of live iPad/provider acceptance; explicitly mark untested items. +6. Keep deployment separate from implementation: no merge, Railway release, storage migration, DNS change, plugin update, or legacy Site retirement based solely on tests. Require verified storage backup, release review, and real-world acceptance. + +## Non-negotiable user contract +- Supported Atmosphere identities authenticate with **one** AT Protocol OAuth sign-in. +- Every authenticated DID gets a private workspace. No second browser/server connection key. +- A verified user can create, save, reopen, edit, export/import, and delete their own projects only. +- A signed-in user cannot access another's list, projects, selected project, sources, messages, imports, exports, deletion, or model context. +- Admin role is separate from record ownership; machine MCP credentials do not grant blanket user workspace access. +- The legacy unowned workspace file must not be silently assigned, overwritten, migrated, or deleted. +- Server outages must fail closed and never switch a logged-in Railway user into anonymous browser storage. +- Never publish private drafts or record a false save success. + +## Required implementation report +Cite the exact documentation read, files changed, constraints preserved, test commands/results, CI commit SHA, and remaining unverified acceptance gates. Report deviations as failures, not as progress. No claims of deployment without verified Railway state. + +These rules are project constraints, not new product decisions. Any change to them requires explicit owner approval and source-linked review. + + +## Canonical runtime and helper — owner amendment, 27 October 2026 + +One Companion application instance: the existing Railway service. The old +ChatGPT Site is a migration source pending verified data/client cutover and +explicit retirement approval, not a second permanent app. ChatGPT is a client. +Preserve original OAuth/MCP dependencies until resolved; no automatic session or +record synchronization. Keep a single persistent contextual helper, with its +restore control visible only while hidden. ChatGPT instructions belong inside +that menu, not in duplicate footer/owner-only setup controls. Use explicit +project-information consent language, not a blanket claim of no private access. +Use deployment/RAILWAY.md's existing cutover gates; consolidation is incomplete +until one canonical runtime serves users and the old writable runtime is retired. diff --git a/COMPANION-HOSTING-STATUS.md b/COMPANION-HOSTING-STATUS.md index 2b07c07..53f415f 100644 --- a/COMPANION-HOSTING-STATUS.md +++ b/COMPANION-HOSTING-STATUS.md @@ -1,3 +1,17 @@ +## Current owner direction — 27 October 2026 + +The one canonical Companion application runtime is the existing Railway service: +https://pixie-project-companion-production.up.railway.app/. +This repository's public guide is navigation, and ChatGPT is a client integration. +The old ChatGPT Site is a temporary migration source to retire after accepted +replacement/data/client verification and explicit approval, not a permanent +second writable Companion. Prepared navigation routes to Railway. Existing +source identities, OAuth callbacks and installed endpoints are preserved while +migration dependencies remain unresolved. Where dated hosting/DNS/source claims below differ, this direction supersedes +them. Existing build and contribution instructions remain applicable. No DNS or +hosting change is authorized here. Follow the existing +[canonical cutover inventory](apps/companion/deployment/RAILWAY.md). + # Companion hosting status — 18 October 2026 pixie.loptrlab.com is the public guide and contributor entrance, deployed from this repository through GitHub Pages. Its PIXIE disclosure button links public instructions, the local start/resume section, and the optional interactive companion. @@ -5,3 +19,12 @@ pixie.loptrlab.com is the public guide and contributor entrance, deployed from t The interactive companion remains at https://pixie-project-companion.ibloud-ivxx.chatgpt.site/ . Its server endpoints and MCP integration have not migrated. GitHub Pages cannot execute them. Do not describe the public guide as the migrated interactive runtime or as shared memory across projects. A later migration must preserve project data and tool behavior, replace host-specific authentication, test anonymous contributor access and MCP authorization, and verify the replacement before switching links or retiring the current backend. No new hosting service, DNS change, or backend deployment was introduced by this button update. + +## Independent application source candidate — 26 October 2026 + +Owner direction selects this existing repository as the preferred GitHub source +home for a separate `apps/companion` module. The guide build and current public +runtime links are unchanged. See the module README for recovered source lineage, +independent build/HTTP/MCP commands, local verification and outstanding clean-install, +authentication, accessibility, hosting and mirror acceptance. No deployed runtime, +plugin endpoint, OAuth origin, source mirror or domain has migrated by this review. diff --git a/HOSTING-AND-DNS.md b/HOSTING-AND-DNS.md index 054bc21..07b2580 100644 --- a/HOSTING-AND-DNS.md +++ b/HOSTING-AND-DNS.md @@ -1,3 +1,17 @@ +## Current owner direction — 27 October 2026 + +The one canonical Companion application runtime is the existing Railway service: +https://pixie-project-companion-production.up.railway.app/. +This repository's public guide is navigation, and ChatGPT is a client integration. +The old ChatGPT Site is a temporary migration source to retire after accepted +replacement/data/client verification and explicit approval, not a permanent +second writable Companion. Prepared navigation routes to Railway. Existing +source identities, OAuth callbacks and installed endpoints are preserved while +migration dependencies remain unresolved. Where dated hosting/DNS/source claims below differ, this direction supersedes +them. Existing build and contribution instructions remain applicable. No DNS or +hosting change is authorized here. Follow the existing +[canonical cutover inventory](apps/companion/deployment/RAILWAY.md). + # PIXIE hosting and DNS Decision recorded 18 October 2026: use the existing public GitHub guide as the diff --git a/README.md b/README.md index 16624ff..3c15095 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,17 @@ +## Current owner direction — 27 October 2026 + +The one canonical Companion application runtime is the existing Railway service: +https://pixie-project-companion-production.up.railway.app/. +This repository's public guide is navigation, and ChatGPT is a client integration. +The old ChatGPT Site is a temporary migration source to retire after accepted +replacement/data/client verification and explicit approval, not a permanent +second writable Companion. Prepared navigation routes to Railway. Existing +source identities, OAuth callbacks and installed endpoints are preserved while +migration dependencies remain unresolved. Where dated hosting/DNS/source claims below differ, this direction supersedes +them. Existing build and contribution instructions remain applicable. No DNS or +hosting change is authorized here. Follow the existing +[canonical cutover inventory](apps/companion/deployment/RAILWAY.md). + # PIXIE Ecosystem Guide This is a plain-language, out-of-world guide to PIXIE’s place across Loptr Lab. It explains how a deliberately transmedia story can be discovered across separate media, while the studio’s repositories also contain genuinely separate stories, planning documents, prototypes, and code references at different stages. The guide maps those pieces; it does not connect them into one service or declare every link part of one fictional continuity. @@ -43,3 +57,13 @@ Use this existing GitHub repository as PIXIE’s public guide/front door. Keep t working Project Companion Site and plugin identity in place during acceptance. See [HOSTING-AND-DNS.md](HOSTING-AND-DNS.md) for the exact DNS record, verified deployment blocker and separation between source development and server hosting. + +## Companion application source review + +The owner selected this existing repository as the preferred source home for a +separate Companion module, subject to preserving this guide. The recovered, +independently runnable application is under [apps/companion](apps/companion/README.md). +Its build, tests and Node/MCP runner are independent of the root guide build. +This source review does not deploy the module, migrate the existing Site/plugin, +change domains or establish mirror synchronization. Follow the module's explicit +acceptance and Site-retirement gates; the guide's existing functionality remains. diff --git a/apps/companion/.gitignore b/apps/companion/.gitignore new file mode 100644 index 0000000..53114e7 --- /dev/null +++ b/apps/companion/.gitignore @@ -0,0 +1,9 @@ +node_modules +tangled-site/ +*.tsbuildinfo +.env* +review-evidence/ + +.pixie-data/ + +.sites-runtime/ diff --git a/apps/companion/README.md b/apps/companion/README.md new file mode 100644 index 0000000..dce6ed8 --- /dev/null +++ b/apps/companion/README.md @@ -0,0 +1,379 @@ +## Canonical runtime amendment — owner direction, 27 October 2026 + +The existing Railway deployment is PIXIE Companion's one canonical application +runtime: https://pixie-project-companion-production.up.railway.app/. +The old ChatGPT Site is a migration source pending acceptance and explicit +retirement approval, not a permanent second Companion. ChatGPT connects as a +client with separate permissions. Guide/Creator navigation is prepared for this +coordinated cutover; auth metadata and original adapters are intentionally retained +until their dependencies are resolved. See the existing +[cutover inventory and gates](deployment/RAILWAY.md#one-canonical-companion--coordinated-cutover-owner-direction-27-october-2026). +No production data transfer, client update or Site retirement is executed here. +Dated statements below about the Site being live are historical transition status, +not a decision to maintain multiple writable application instances. + +# PIXIE Project Companion — independent source review + +This is the recovered Companion application module inside the existing PIXIE +guide repository. It is executable source, not a replacement guide or a released +hosting migration. The guide's root application, build and Pages workflow remain +separate and unchanged. Existing ChatGPT Site/App/plugin identities remain live. + +## Source and ownership + +Owner direction on 26 October 2026 selected this repository, subject to preserving +the guide, as the preferred existing GitHub home. This review module was recovered +from local reconciled source `ee595556dfd352fdcb6342ea5de20b6688f79ff8`. +That history retains Sites v17 `73ef3b57` and Tangled's `2214950e` / `90287b0d`. +`source-recovery.json` distinguishes preserved, adapted and new files by hash. +No private Site bindings, credentials, caches, unrelated starter components or +private participant records were imported. The original checkouts and archives +are retained. No new license or rights transfer is implied by source availability. + +The task contract and independent Node runner are new implementation. Shared +project/resource operations, contributor checkpoints, identity UI, styles and +mirror safeguards are recovered and extended, rather than recreated. Retained +`app/api/companion` and `app/mcp` files document the original Sites adapters; +`server.mjs` supplies the independent HTTP adapters. Neither trusts the other's +authentication mechanism implicitly. + +## Build and run outside Sites + +Requires Node >=22.19.0 and the pinned pnpm version in package.json. + +```sh +cd apps/companion +pnpm install --frozen-lockfile +pnpm test +pnpm run typecheck +pnpm run build +pnpm start +``` + +The runner defaults to `127.0.0.1:4173`. `HOST` and `PORT` select an explicitly +approved deployment's bind address; changing them does not provision or deploy a +service. The existing Vite build writes `tangled-site/`, ignored generated output. +The guide's root build does not copy or publish this module. + +Frozen installation succeeded using the verified local pnpm cache after restoring +`pnpm-workspace.yaml`, which was omitted from recovery. Locked package versions +and the supply-chain policy were preserved. This is not evidence of a fresh +network installation on another machine. + +## Persistent creator workspace + +My projects supports creating, selecting, editing, archiving and restoring projects; +purpose, outcome, state and next action; tasks with progress, blockers and completion +evidence; creator notes; sources with separately labelled supplied excerpts and +retrieval results; and editable, downloadable handoffs. Backup import validates the +whole file before saving and never overwrites existing project IDs. Conflicts can +be imported explicitly as distinct copies. Export before changing hosting origins. +Unsaved edits are excluded from exports and warned about when switching projects. + +The independent runner saves each verified DID's workspace in a server-derived +hashed file under `users/` beside `PIXIE_DATA_FILE`. The original workspace file is +legacy unowned data and remains unchanged. Atomic writes, private permissions and +exclusive per-file locks protect both project and encrypted identity storage. +Production browser access uses the existing server-verified Atmosphere session; +no browser key is required. Admin metadata does not grant other users' records. +The machine bearer works only on MCP and grants no unscoped workspace access. +Loopback development retains the existing explicit local-owner mode. Back up the +private volume and user exports; Git is not a data backup. + +A static-only web preview instead uses that browser/origin's IndexedDB, explicitly +labelled in the interface. It survives reloads and normal browser restarts but not +browser-data deletion. It does not share data with the independent MCP, other +origins or other devices. Private browsing can discard it. No synchronization is +active. Exports contain project content, not connection keys. + +Conversation is enabled only when the independent server has all three private +settings: `PIXIE_MODEL_URL` (HTTPS OpenAI-compatible chat-completions endpoint), +`PIXIE_MODEL_API_KEY`, and `PIXIE_MODEL_NAME`. The user explicitly consents before +sending selected-project context. Bounded recent notes, excerpts, tasks and messages +are sent to that provider; other projects and credentials are excluded. Answers +are labelled AI suggestions, execute no actions, and do not become verified facts. +No provider is configured in this checkpoint: the interface reports unavailable +and saves no fabricated reply. Live provider acceptance remains outstanding. +Public-source checks currently support public GitHub text files; unsupported or +failed checks remain visible as failures beside the retained supplied source. + +## HTTP and MCP boundaries + +- `/`: independent browser workspace. Find help, Project work and Task stewardship + share the existing service functions. Browser-static mode executes validated + read/draft operations locally; external public-source reads still require + provider availability and CORS. No private database or automatic posting. +- `POST /api/companion`: same supported read/draft operations, same-origin browser + checks, bounded request body and no server task persistence. +- `POST /mcp`: shared `handleRpc`/`runTool` operations with independent bearer + authentication. Configure `PIXIE_MCP_TOKEN` privately with at least 32 characters; + never commit or embed it in browser code. Without it MCP fails closed with 503; + invalid credentials return 401. Fake Sites identity headers do not authorize it. +- `/health`: reports runner mode and whether MCP authentication is configured; + never reveals a credential. A health response is not release acceptance. + +This minimal bearer adapter is a tested transport boundary, not a completed +multi-user OAuth installation flow. Real ChatGPT/other-client connection and +per-client authorization/revocation require review before public service release. +Do not expose an unconfigured endpoint or treat web identity sign-in as MCP auth. +Atmosphere identity uses the existing AT Protocol provider, account panel and +exact reviewed HTTPS origins in `lib/identity-origin.mjs`. The original Site +keeps the browser SDK and its unchanged +`/oauth-client-metadata.json` and root redirect. The Railway origin +`https://pixie-project-companion-production.up.railway.app` uses the public +`/oauth-client-metadata-railway.json` and its own root redirect. Other origins +cannot start OAuth here and retain the link to the original Site. No Host header, +query parameter or imported record selects a redirect. These are independent +origin-bound sessions, not synchronized accounts. Railway's root callback is now +verified by the official server SDK, pinned at `@atproto/oauth-client-node@0.5.10`. +Client ID, metadata, redirect and atproto-only scope remain unchanged. No provider +or domain is added. Browser JavaScript cannot assert ownership or supply an issuer, +redirect, DID or bearer credential to authorize private workspace operations. + +### Atmosphere identity and isolated project storage + +The published Railway Companion must support **one Atmosphere sign-in per person**. +Any supported AT Protocol identity can authenticate. The server verifies the OAuth +session and the returned DID before authorizing browser operations. Each verified +DID resolves to a distinct server-owned hashed filename under the existing private +volume's `users/` directory. No handle, path, user ID or ownership claim supplied +in an API request may select another person's workspace. Each user's project list, +selected project, edits, exports, imports, deletion and model context are isolated. + +`PIXIE_SESSION_SECRET` is a separate 32-byte random hex encryption key (64 +lowercase hex characters), stored **only** in Railway's private variables, never +in browser code, screenshots, issues or shared backups. `PIXIE_OWNER_DID` is +optional administrative metadata; it is **not** an admission list or a grant to +other people's projects. A configured admin still sees only their own workspace. +`PIXIE_MCP_TOKEN` is exclusively for machine MCP authentication and does not +implicitly authorize any user's private project data. Unscoped machine requests +for workspace operations are denied until a separately reviewed principal/grant +model exists. Preserve the existing Site OAuth configuration. + +The server uses the existing AT Protocol Node OAuth SDK, encrypted state storage, +opaque Secure/HttpOnly/SameSite=Lax cookies, explicit Host/Origin and CSRF checks, +and server-verified, short-lived sessions. Sign-out and session expiry deny access +without deleting saved projects or other people's sessions. There is **no** second +browser key, no new database/service, and no automatic migration. + +The existing `PIXIE_DATA_FILE` (for example `/data/workspace.json`) is legacy, +**unowned** project data. Keep it untouched and unassigned until ownership is +confirmed and a separately reviewed migration/export is approved. Never treat the +first DID to sign in as its owner. Keep the persistent volume, encrypted identity +state, backups and one-writer restriction intact. Railway failure must not cause +fallback to an anonymous browser workspace masquerading as server data. + +Verification includes two independent synthetic accounts and full sign-in → +create → save → reopen → sign-out flows; cross-user read/write/export/import/erase +and machine access must fail. These tests do not replace live provider, iPad Safari, +VoiceOver, session-restoration, persistence or restart acceptance. Do not merge, +deploy, change the domain or retire the original Site without a separate release +review and authorization. + +## Supported operations: read versus draft versus execution + +| Operations | Behavior | +| --- | --- | +| list_projects, get_project_status, discover_resources, get_resource, resume_checkpoint | Read curated/public sources; retain dates and retrieval limitations. | +| suggest_contribution, prepare_feedback, prepare_checkpoint, prepare_enquiry | Prepare proposals/drafts. GitHub submission and professional contact remain user actions. | +| export_project_handoff, prepare_followup, resume_followup | Prepare/validate explicit portable context. No automatic synchronization or server storage. | +| prepare_task_contract, update_task_contract, resume_task_contract | Maintain explicit task context; scope fields stay fixed during updates. Imported context requires fresh review. | +| check_task_action | Reports in-scope/out-of-scope/approval requirement. Executes nothing and grants no credentials. | + +No supported operation publishes, deploys, merges, sends correspondence, spends, +grants repository access or approves canon. Explicit confirmed workspace deletion +affects only saved creator records; it does not delete repositories or sources. A scope check cannot constrain +unrelated tools in a host, and an editable file is not cryptographic authorization. +The companion's own task updates cannot silently change outcome, project, actions, +constraints or completion criteria. A new scope requires a new explicitly reviewed +task. Reported completion requires evidence but is not verified completion. + +## Portable continuity and privacy + +Task stewardship records outcome, supported actions, constraints, completion +criteria, status, progress, next action and evidence. Download/import preserves +task ID and revision. Edits must update the working contract before export, and +every import/update resets the public-safe review gate. Changing the selected +project does not silently discard or reassign the working task. + +For public task stewardship, only export public-safe material. Its working text +remains in tab memory; downloaded files belong to the user. The separate My projects +workspace persists as described above; inspect private content before sharing exports. Existing Remember/Forget retains only a public GitHub +issue pointer on that device. GitHub issues remain the shared contribution record. +No opaque chat memory, cross-domain draft transfer or synchronization is claimed. +MCP clients/hosting providers have their own retention. Do not enter credentials, +private briefs, health/benefits records or confidential correspondence. + +## Tests and accessibility + +`pnpm test` covers existing project/resource/checkpoint behavior, task round-trips, +scope rejection, forged imports, completion evidence, independent HTTP/MCP parity, +fail-closed authentication and the existing mirror safety fixtures. + +`pnpm run check:browser` uses a separately available Playwright/Chromium runner; +set `PIXIE_PLAYWRIGHT_MODULE` to its module path, `PIXIE_CHROMIUM` to the browser +executable, and `PIXIE_BASE_URL` to the local runner. It does not deploy or submit +anything. Evidence defaults to ignored `review-evidence/`. Browser tooling is not +an application runtime dependency. + +Physical iPad Safari/Files, VoiceOver, a desktop screen reader, clean install, +independent live-source checks and a separate contributor's plugin installation +remain acceptance gates. Never substitute a build, viewport emulation or an +automated contrast check for those observations. Preserve existing identities, +styles, explicit consent and source-first behavior in future changes. + +## Existing plugin and Tangled mirror + +The installed plugin remains the existing owner-identified 1.0.4 release; the +local historical public-submission manifest says 1.0.3 and is not a verified new +release. Installed tools still expose the previous catalog; these new task tools +are source candidates, not installed changes. Plugin search did not return an +editable PIXIE release ID. No replacement plugin or guessed release was created. + +The existing public Tangled mirror was read without mutation at +`https://tangled.org/made-sick.org/pixie-project-companion`: main advertised +`90287b0d64475df606667078d9bb98bbfc07651a`, with no tags in that response. +It is NOT synchronized with this new GitHub module. The source roots differ: +this repository contains the guide plus `apps/companion`; the existing mirror +contains the older Companion at its root. Do not push the whole guide repository +through the retained mirror helper, force-update the mirror, activate automation, +or invent a second mirror. Agree and verify a history-preserving module mapping +under separate authorization after source review. Until then report not synced. + +## Release and Site-retirement gate + +Do not merge/deploy this module, change domains, repoint the existing plugin or +delete the Site merely because local tests pass. First verify clean installation, +independent hosted behavior, live source access, accessibility, auth/client update, +mirror mapping and every Site-bound dependency. Record exact source/build/client +and human acceptance evidence. Source recovery, review publication, deployment, +plugin update and Site retirement are distinct actions. + +## Current local verification and mirror review + +`pnpm test` includes persistent workspace round-trips, isolated project operations, +validated non-overwriting imports, corrupt-file preservation, unavailable-model +behavior, provider-context isolation, cross-user API denial, and unscoped MCP denial. Run +`node tests/browser-workspace.cjs` after building, with the same Playwright/Chromium +variables described above. It starts its own loopback servers and temporary file +store; it never erases an existing workspace. It checks both server and static +storage, browser restart, handoff edits, export/clear/restore, reading preferences, +keyboard skip navigation and 320–1440 px reflow. Test data is fictional. + +The retained whole-root mirror helper now refuses this module layout. +`scripts/prepare-module-mirror.mjs` prepares only a local review bundle from pinned +source and Tangled commits. It projects `apps/companion` to the existing mirror +root and retains both histories as parents. Projected commit IDs differ from +GitHub IDs; exact module tree identity is recorded. Review deletions, ownership, +privacy, hosting/build triggers and rollback before authorizing any application. +There is no push mode and synchronization remains inactive. + +The existing Site MCP at +`https://pixie-project-companion.ibloud-ivxx.chatgpt.site/mcp` remains separate and +unchanged. A Vercel static preview serves the web interface only. This independent +MCP has source tests, but hosted deployment and plugin connection remain unfinished. + +## Existing Railway service preparation + +Service-specific Railpack/start/readiness configuration is in `railway.json`. +The existing guide root build is untouched. Use the fail-closed production launcher +`node scripts/start-production.mjs`, with privately configured machine and identity secrets, +local-owner bypass disabled and a real persistent volume. See +[deployment/RAILWAY.md](deployment/RAILWAY.md) for exact existing-service settings, +non-secret variables, verification evidence, rollback and current access blockers. +The 9e836768 pin below is a historical rollback reference. Use the dated current release in deployment/RAILWAY.md and re-read Railway before any approved release; this review does not release the candidate. +The static Vercel preview continues to use browser storage and hosts no MCP. + +## Corrective security review + +OAuth uses the official SDK's existing locked Node safety adapter with native +fetch. HTTPS, DNS/private-address and redirect-hop checks stay attached to its +transport. It does not use the unrelated Wrangler proxy dispatcher. Anonymous +session-status reads no longer consume pending-login slots; actual login attempts +retain bounded, cookie-bound CSRF-protected transactions. Session capability +responses identify the verified DID so resumed tabs clear a previous account's +private view before reopening the current workspace. + +Private file operations use an exclusive per-file lock shared across Node +processes. Lock contention fails without acknowledging a save. Keep one Railway +replica; this is not distributed storage. A crash may leave a `.lock` file: stop +all writers, back up and validate the data, confirm no writer remains, and remove +only the verified stale lock under separate operational approval. Never break a +lock automatically or discard saved data to recover. + +The behavioral suite includes the real SDK transport boundary, synthetic A/B +account isolation, nested-record/import rejection, expired sessions, CSRF, +corrupt-file preservation, independent writers and anonymous login-capacity +protection. `node tests/browser-owner.cjs` additionally checks account switching, +legacy-data preservation, dark/light text contrast and restart recovery. Browser +provider cryptography is synthetic; live OAuth and physical iPad acceptance remain +required. Browser checks currently require separately available tooling and are +not claimed as enforced GitHub checks. + +## Creator OS handoff — review only + +The selected private project can explicitly open the existing Creator OS at +https://ibloud.github.io/pixie-creator-os/ after a consent prompt. Only project +name/purpose and its last reviewed creative draft leave this tab. Exact origin +and opened-window checks bind the exchange; no bearer, CSRF or OAuth token is +sent. The returned draft is shown for review and saved as a creator note through +the existing workspace API only after confirmation and a fresh verified-DID check. +The granted project ID is fixed; a browser-supplied target cannot replace it. +Creator OS retains media preparation, source-aware references and manual sharing. +No new identity provider, PDS record, database, scope or automatic publication is +introduced. Sequential retries of the identical receipt do not append twice. + +Both review candidates are required; current public Creator OS and pinned Railway +production are unchanged. Reopen the same private project after sign-out to resume +the saved draft, reselect media and review publishing again. Unsaved returns are +not persistence. Logout/identity change revokes the handoff; exports already +chosen by a user cannot be remotely revoked. A severed opener or failed connection +must retain the existing file handoff rather than trust arbitrary origins. +GitHub Pages shares ibloud.github.io across repositories; release review must +accept that origin's trust boundary. No public deployment is authorized here. + +Local cross-application test: set PIXIE_CREATOR_ROOT to the reviewed existing +Creator OS checkout, then run node tests/browser-creator.cjs with the existing +Playwright/Chromium variables. The upstream provider is synthetic; physical iPad, +live OAuth, private hosting and Railway backup/restart acceptance remain separate. +The existing shared account picker also keeps a submit pointer gesture stable: +closing suggestion results/status on blur must not move Continue before click. +This changes no provider, session, metadata or OAuth permission. Original Site and +static-origin identity regression fixtures remain required. + + +## Source-check outcome and retrieval boundary + +The existing shared `workspace_check_source` operation returns the saved project, +source record, an `ok` boolean and a readable `notice`. The browser displays that +notice; MCP returns the same validated records and marks a failed check +`isError: true` even though its failure record was saved successfully. A fetched +page is not verified fact. Prior excerpt/date, source URL/title, creator notes +and project identity survive a failed retry; `checkState: failed` and `lastError` +distinguish the latest failed attempt. No backup/storage schema changes. + +Only the existing fixed public GitHub contents API is contacted, with manual +redirect handling and a ten-second timeout. The response is now limited to +200,000 decoded bytes **while reading**, not after allocating its entire text. +Oversized bodies cancel their reader; rejected declared lengths cancel before +reading. Failed, unsupported and aborted checks retain recoverable work. No +arbitrary URL fetcher, additional provider, public-by-link store or run engine +is added. Long-operation progress/user cancellation and quote matching remain +unimplemented; this correction does not claim them. + +Pattern review: [Osiris MCP results](https://github.com/simplifaisoul/osiris/blob/085b2429b11b2b1af43880caaa088575cc98c14f/src/lib/oi/mcp.ts) +separate readable summaries, structured data and tool errors. Its source quote +matching checks text presence, not truth; its process-memory public run store is +unsuitable for PIXIE's private continuity. Its shared service, injected provider +and bounded network patterns informed this review. This correction independently +extends PIXIE's existing code; no Osiris code was copied or dependencies added. +Osiris is MIT-licensed, copyright 2026 simplifaisoul. If code is copied in future, +retain its full copyright and license notice. Existing analytics, IP logging, +surveillance UI and forecasting engine are not adopted. + +Verification extends existing workspace and identity test suites with streamed +size/abort/recovery checks, matching HTTP/MCP outcomes on the same service state, +and cross-user/ungranted-machine denial. Browser acceptance must show the saved +failure state and notice without reporting success. The existing helper and +central styles are unaffected. This is source review only; no deployment or +live-provider acceptance is implied. diff --git a/apps/companion/app/account.tsx b/apps/companion/app/account.tsx new file mode 100644 index 0000000..5cf9265 --- /dev/null +++ b/apps/companion/app/account.tsx @@ -0,0 +1,50 @@ +'use client'; +import {useEffect,useRef,useState} from 'react'; +import {identityConfiguration,siteIdentityOrigin} from '../lib/identity-origin.mjs'; +import {serverIdentity} from '../lib/server-identity-browser.mjs'; + +type IdentityUI = {destroy:()=>void}; +declare global { + interface Window { PixieIdentity?: {mount:(element:HTMLElement,options?:Record)=>IdentityUI}; } +} +const loads = new Map>(); +const publishedIdentity=identityConfiguration(siteIdentityOrigin)!; +function loadScript(src:string) { + if (!loads.has(src)) loads.set(src,new Promise((resolve,reject)=>{ + const script=document.createElement('script');script.src=src;script.async=true; + script.onload=()=>resolve();script.onerror=()=>{loads.delete(src);script.remove();reject(new Error('Account tools could not load.'));}; + document.head.appendChild(script); + })); + return loads.get(src)!; +} +export default function Account(){ + const panel=useRef(null);const details=useRef(null);const [error,setError]=useState('');const [connected,setConnected]=useState(false); + useEffect(()=>{ + let disposed=false,ui:IdentityUI|undefined,observer:MutationObserver|undefined; + const open=()=>{if(window.location.hash==='#account'&&details.current)details.current.open=true;};open(); + window.addEventListener('hashchange',open); + loadScript('./shared/atproto-account.js?v=1').then(()=>loadScript('./shared/atproto-sign-in.js?v=2')).then(async()=>{ + const serverAuth=await serverIdentity(); + if(!disposed&&panel.current){ + const config=identityConfiguration(window.location.origin)||publishedIdentity; + panel.current.dataset.clientId=config.clientId; + panel.current.dataset.redirectUri=config.redirectUri; + ui=window.PixieIdentity?.mount(panel.current,{serverAuth}); + const form=panel.current.querySelector('form'); + if(form){observer=new MutationObserver(()=>setConnected(form.hidden));observer.observe(form,{attributes:true,attributeFilter:['hidden']});setConnected(form.hidden);} + } + }).catch(()=>{if(!disposed)setError('Account tools could not load. You can continue without signing in; reload to try again.');}); + const closeOutside=(event:PointerEvent)=>{if(details.current&&!details.current.contains(event.target as Node))details.current.open=false;}; + const escape=(event:KeyboardEvent)=>{if(event.key==='Escape'&&!event.defaultPrevented&&details.current?.open&&!panel.current?.querySelector('[aria-expanded="true"]')){details.current.open=false;details.current.querySelector('summary')?.focus();}}; + document.addEventListener('pointerdown',closeOutside);document.addEventListener('keydown',escape); + return()=>{disposed=true;observer?.disconnect();ui?.destroy();window.removeEventListener('hashchange',open);document.removeEventListener('pointerdown',closeOutside);document.removeEventListener('keydown',escape);}; + },[]); + return
+ {connected?'Account':'Sign in'} +

Your atmosphere account

+ {error&&

{error}

} +
+ +
+
; +} diff --git a/apps/companion/app/api/companion/route.ts b/apps/companion/app/api/companion/route.ts new file mode 100644 index 0000000..84f9cd0 --- /dev/null +++ b/apps/companion/app/api/companion/route.ts @@ -0,0 +1,8 @@ +import { runTool } from '../../../lib/companion.mjs'; +export async function POST(request:Request){ + const origin=request.headers.get('origin'); + if(origin&&origin!==new URL(request.url).origin)return Response.json({error:'Invalid origin'},{status:403}); + const raw=await request.text();if(raw.length>16000)return Response.json({error:'Request too large'},{status:413}); + try{const {name,args}=JSON.parse(raw);return Response.json(await runTool(name,args),{headers:{'Cache-Control':'no-store'}});} + catch(e){return Response.json({error:e instanceof Error?e.message:'Invalid request'},{status:400});} +} diff --git a/apps/companion/app/companion.tsx b/apps/companion/app/companion.tsx new file mode 100644 index 0000000..ea83756 --- /dev/null +++ b/apps/companion/app/companion.tsx @@ -0,0 +1,81 @@ +'use client'; +import {useEffect,useState} from 'react'; +import {checkpointPointer,readCheckpointPointer,checkpointStorageKey} from '../lib/checkpoint-pointer.mjs'; +import projects from '../lib/projects.json'; +import {projectConnections} from '../lib/project-connections.mjs'; +import CreativeHelp from './creative-help'; +import CreatorWorkspace from './creator-workspace'; +import Account from './account'; +import {companionRequest} from '../lib/companion-request'; +import PixieHelper from './pixie-helper'; +import TaskSteward from './task-steward'; +import {principles,accessStatus} from '../lib/principles.mjs'; +type Result = Record; +export default function Companion(){ + const [handoffNotice,setHandoffNotice]=useState(''); + const [view,setView]=useState('creator'); + useEffect(()=>{const sync=()=>setView(window.location.hash==='#task'?'tasks':window.location.hash==='#workspace'?'projects':window.location.hash==='#find-help'?'help':'creator');sync();window.addEventListener('hashchange',sync);return()=>window.removeEventListener('hashchange',sync);},[]); + const [project,setProject]=useState('training');const [mode,setMode]=useState('contribute');const [minutes,setMinutes]=useState(15); + const [result,setResult]=useState(null);const [busy,setBusy]=useState(false);const [error,setError]=useState('');const [copied,setCopied]=useState(false); + const [form,setForm]=useState({summary:'',steps:'',expected:'',actual:'',environment:'',build:''}); + const [checkpoint,setCheckpoint]=useState({task:'',progress:'',blocker:'',next_action:''});const [issueUrl,setIssueUrl]=useState(''); + const [remembered,setRemembered]=useState<{version:number;projectId:string;issueUrl:string}|null>(null);const [storageNotice,setStorageNotice]=useState(''); + useEffect(()=>{try{setRemembered(readCheckpointPointer(localStorage.getItem(checkpointStorageKey),projects));}catch{}},[]); + function remember(){try{const value=checkpointPointer(project,issueUrl.trim(),projects);localStorage.setItem(checkpointStorageKey,JSON.stringify(value));setRemembered(value);setStorageNotice('Checkpoint link remembered on this device.');}catch{setStorageNotice('Could not remember this checkpoint. Use a valid project issue URL, or keep the link yourself.');}} + function forget(){try{localStorage.removeItem(checkpointStorageKey);setRemembered(null);setStorageNotice('Checkpoint link removed from this device. The GitHub issue is unchanged.');}catch{setStorageNotice('Browser storage is unavailable.');}} + function restore(){if(!remembered)return;setHandoffNotice('');setProject(remembered.projectId);setIssueUrl(remembered.issueUrl);setMode('checkpoint');setResult(null);setError('');} + const selected=projects.find(p=>p.id===project)!; + const connections=projectConnections(selected); + let checkpointLink='';try{checkpointLink=checkpointPointer(project,issueUrl.trim(),projects).issueUrl;}catch{} + async function run(){setBusy(true);setError('');setCopied(false);setResult(null);try{ + const name=mode==='resume'?'get_project_status':mode==='contribute'?'suggest_contribution':mode==='coordinate'?'prepare_checkpoint':mode==='checkpoint'?'resume_checkpoint':'prepare_feedback'; + const args=mode==='resume'?{project_id:project,refresh:true}:mode==='contribute'?{project_id:project,minutes}:mode==='coordinate'?{project_id:project,...checkpoint}:mode==='checkpoint'?{project_id:project,issue_url:issueUrl.trim()}:{project_id:project,...form}; + const response=await companionRequest({name,args}); + const value=await response.json() as Result;if(!response.ok)throw new Error(value.error||'Could not complete the request.');setResult(value); + }catch(e){setError(e instanceof Error?e.message:'Could not complete the request.');}finally{setBusy(false);}} + function changeMode(value:string){setMode(value);setResult(null);setError('');} + async function exportHandoff(){ + setBusy(true);setError('');setHandoffNotice(''); + try{ + const response=await companionRequest({name:'export_project_handoff',args:{project_id:project}}); + const value=await response.json() as Result; + if(!response.ok)throw Error(value.error||'Export unavailable.'); + const url=URL.createObjectURL(new Blob([JSON.stringify(value,null,2)+'\n'],{type:'application/json'})); + const link=document.createElement('a');link.href=url;link.download='pixie-'+project+'-handoff.json';link.click(); + setHandoffNotice('Download prepared. Check your downloads to keep the JSON file. No shared record was saved or submitted.'); + setTimeout(()=>URL.revokeObjectURL(url),1000); + }catch(e){setError(e instanceof Error?e.message:'Export unavailable.');} + finally{setBusy(false);} + } + async function copy(){try{await navigator.clipboard.writeText(result?.report||'');setCopied(true);}catch{setError('Copy is unavailable. Select the report text below to copy it.');}} + return
Skip to main content +
PIXIE LOPTR LAB
+