@mriekki/project-forge 4.26.1 → 4.27.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/assets/authentication/distribution.json +90 -0
  2. package/assets/components.json +17 -3
  3. package/assets/core/distribution.json +12 -0
  4. package/assets/deployment/distribution.json +292 -68
  5. package/assets/generated/ASSEMBLY.json +150 -4
  6. package/assets/generated/authentication/docs/AUTH_RECOVERY_RETENTION.md +101 -0
  7. package/assets/generated/authentication/docs/HOUSEHOLD_INFRASTRUCTURE_ADOPTION.md +55 -0
  8. package/assets/generated/authentication/docs/HOUSEHOLD_REGISTRY.md +246 -0
  9. package/assets/generated/authentication/docs/PROJECT_AUTH_REFERENCE.md +878 -0
  10. package/assets/generated/authentication/samples/ProjectForgeAuth.Api/Program.cs +40 -0
  11. package/assets/generated/authentication/samples/ProjectForgeAuth.Api/ProjectForgeAuth.Api.csproj +13 -0
  12. package/assets/generated/authentication/samples/ProjectForgeAuth.Api/wwwroot/auth.js +65 -0
  13. package/assets/generated/authentication/samples/ProjectForgeAuth.Api/wwwroot/index.html +44 -0
  14. package/assets/generated/authentication/src/ProjectForge.Auth/AdmissionPolicy.cs +167 -0
  15. package/assets/generated/authentication/src/ProjectForge.Auth/AuthenticationDiagnostics.cs +159 -0
  16. package/assets/generated/authentication/src/ProjectForge.Auth/ProjectForge.Auth.csproj +9 -0
  17. package/assets/generated/authentication/src/ProjectForge.Auth/PwaExistingProjectCompatibility.cs +64 -0
  18. package/assets/generated/authentication/src/ProjectForge.Auth.Runtime/ProjectAuthStore.cs +471 -0
  19. package/assets/generated/authentication/src/ProjectForge.Auth.Runtime/ProjectForge.Auth.Runtime.csproj +15 -0
  20. package/assets/generated/authentication/src/ProjectForge.Auth.Runtime/ProjectForgeAuthRuntimeExtensions.cs +472 -0
  21. package/assets/generated/authentication/src/ProjectForge.Auth.Runtime/ResendProjectInvitationDelivery.cs +105 -0
  22. package/assets/generated/authentication/src/ProjectForge.Transport/ProjectForge.Transport.csproj +11 -0
  23. package/assets/generated/authentication/src/ProjectForge.Transport/StrictPublicOriginTransport.cs +95 -0
  24. package/assets/generated/authentication/tools/household_connect.py +145 -0
  25. package/assets/generated/authentication/tools/project_auth_recovery.py +716 -0
  26. package/assets/generated/core/core/process/ENGINEERING_BACKLOG.md +150 -0
  27. package/assets/generated/core/core/skills/manage-backlog/SKILL.md +115 -0
  28. package/assets/generated/core/core/skills/review-user-feedback/SKILL.md +50 -0
  29. package/assets/generated/deployment/docs/RECOVERY_READINESS.md +103 -0
  30. package/assets/generated/deployment/src/project_forge_deployment/auth_recovery_capture.py +164 -0
  31. package/assets/generated/deployment/src/project_forge_deployment/auth_recovery_guard.py +225 -0
  32. package/assets/generated/deployment/src/project_forge_deployment/auth_recovery_lifecycle.py +455 -0
  33. package/assets/generated/deployment/src/project_forge_deployment/auth_recovery_retention.py +220 -0
  34. package/assets/generated/deployment/src/project_forge_deployment/auth_recovery_schedule.py +489 -0
  35. package/package.json +1 -1
  36. package/src/cli.mjs +32 -3
  37. package/src/engine.mjs +87 -5
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "schemaVersion": 2,
3
3
  "package": "@mriekki/project-forge",
4
- "version": "4.26.1",
4
+ "version": "4.27.1",
5
5
  "components": {
6
6
  "core": {
7
- "revision": "670b84af788323ce361a49677aff772c0d1541c4",
7
+ "revision": "eac51d50a9e3c8931159ee8dedba79e63ba7867b",
8
8
  "version": "4.3.1",
9
9
  "distribution": "greenfield-workspace",
10
10
  "files": [
@@ -368,6 +368,21 @@
368
368
  "destination": ".github/skills/ux-review/SKILL.md",
369
369
  "sha256": "2d0031a7041b8174ef056cf35015d5a9faadbc960313b3d1117bf55b8cb44486"
370
370
  },
371
+ {
372
+ "source": "core/skills/review-user-feedback/SKILL.md",
373
+ "destination": ".github/skills/review-user-feedback/SKILL.md",
374
+ "sha256": "80f6087b222b76d93b92e5cbacf33ac1af1a6e797bf27b796b7d70b34280d5e0"
375
+ },
376
+ {
377
+ "source": "core/process/ENGINEERING_BACKLOG.md",
378
+ "destination": "docs/process/ENGINEERING_BACKLOG.md",
379
+ "sha256": "49f3f6d4f48ffed74c8a22e075a8efe1c04c6f40d1c3b978184e4c29a6e00bfa"
380
+ },
381
+ {
382
+ "source": "core/skills/manage-backlog/SKILL.md",
383
+ "destination": ".github/skills/manage-backlog/SKILL.md",
384
+ "sha256": "23561dfe3d5010ea9bfbb41ae7657734c40477d43303b56f4a05ac8fd8ad6ef8"
385
+ },
371
386
  {
372
387
  "source": "core/instructions/project-forge-web-guidance.instructions.md",
373
388
  "destination": ".github/instructions/project-forge-web-guidance.instructions.md",
@@ -386,7 +401,7 @@
386
401
  ]
387
402
  },
388
403
  "deployment": {
389
- "revision": "e9ca6e12e5b19f691b25ce0aba758a43c3df2913",
404
+ "revision": "0f80ad06eedeeeb84fcf70cd367873b23d70af25",
390
405
  "distribution": "greenfield-project",
391
406
  "files": [
392
407
  {
@@ -599,6 +614,31 @@
599
614
  "destination": "tools/project-forge-deployment/project_forge_deployment/proxy_peer.py",
600
615
  "sha256": "ae6c8cb26b83ed07765fa4740ce5c68f52cafd2209b4f055887a4457cfb41c03"
601
616
  },
617
+ {
618
+ "source": "src/project_forge_deployment/auth_recovery_guard.py",
619
+ "destination": "tools/project-forge-deployment/project_forge_deployment/auth_recovery_guard.py",
620
+ "sha256": "8ca97923b994f07655b04062556e2dd755c2127ff134ecbfe64c64f20f0e5ac8"
621
+ },
622
+ {
623
+ "source": "src/project_forge_deployment/auth_recovery_capture.py",
624
+ "destination": "tools/project-forge-deployment/project_forge_deployment/auth_recovery_capture.py",
625
+ "sha256": "de8e2db1d66b07d69afe11aa331264cc180cd9b3ecf734c175cd1fdff5764d4c"
626
+ },
627
+ {
628
+ "source": "src/project_forge_deployment/auth_recovery_lifecycle.py",
629
+ "destination": "tools/project-forge-deployment/project_forge_deployment/auth_recovery_lifecycle.py",
630
+ "sha256": "ade089a5b1e9ee98ca06e81f6629b07f3786e65a106f3bdd0c3639cfccd038a8"
631
+ },
632
+ {
633
+ "source": "src/project_forge_deployment/auth_recovery_schedule.py",
634
+ "destination": "tools/project-forge-deployment/project_forge_deployment/auth_recovery_schedule.py",
635
+ "sha256": "10e7e9b8d2f1b24c9abb1f10aebbd9a89677896ee4d51d33d12f8a0f174bed23"
636
+ },
637
+ {
638
+ "source": "src/project_forge_deployment/auth_recovery_retention.py",
639
+ "destination": "tools/project-forge-deployment/project_forge_deployment/auth_recovery_retention.py",
640
+ "sha256": "953a5e4ef790a61b6ba8742352a89b97ace346b116033e03831c9a19dd7e6288"
641
+ },
602
642
  {
603
643
  "source": "src/project_forge_deployment/recovery_scope.py",
604
644
  "destination": "tools/project-forge-deployment/project_forge_deployment/recovery_scope.py",
@@ -712,7 +752,7 @@
712
752
  {
713
753
  "source": "docs/RECOVERY_READINESS.md",
714
754
  "destination": "docs/project-forge/RECOVERY_READINESS.md",
715
- "sha256": "ae94a858c9431ad1f432cb75e76b532d83f69c8ca163707ac12d9ea11c60edc1"
755
+ "sha256": "61d4b26d5f126acbc544925489acb3adc8d6c7ea5d2d02b4b71bf6ccb3d3171b"
716
756
  },
717
757
  {
718
758
  "source": "docs/images/developer-environment-journey.svg",
@@ -730,6 +770,112 @@
730
770
  "sha256": "357896c40ff2c7a93bf77811af61d2559103d2e386a5fd887c7041d016114b10"
731
771
  }
732
772
  ]
773
+ },
774
+ "authentication": {
775
+ "revision": "6db1c1e8a0d3cb0a20aa3415ca0e228f8849df46",
776
+ "distribution": "greenfield-project",
777
+ "files": [
778
+ {
779
+ "source": "src/ProjectForge.Auth/AdmissionPolicy.cs",
780
+ "destination": "tools/project-forge-auth-kit/src/ProjectForge.Auth/AdmissionPolicy.cs",
781
+ "sha256": "74b76dd227ff3dd5545e14ff69283ef02dd06017165bf35189bf99e041103f22"
782
+ },
783
+ {
784
+ "source": "src/ProjectForge.Auth/AuthenticationDiagnostics.cs",
785
+ "destination": "tools/project-forge-auth-kit/src/ProjectForge.Auth/AuthenticationDiagnostics.cs",
786
+ "sha256": "b1c0d112e963bc3122b56c1a5459c2d3e6575d391433b85f6e02d0bc4fbb58a2"
787
+ },
788
+ {
789
+ "source": "src/ProjectForge.Auth/ProjectForge.Auth.csproj",
790
+ "destination": "tools/project-forge-auth-kit/src/ProjectForge.Auth/ProjectForge.Auth.csproj",
791
+ "sha256": "25d111608dc0c1dd47702cd0501f7de2e20d67552ff7e1c3779722985c27641e"
792
+ },
793
+ {
794
+ "source": "src/ProjectForge.Auth/PwaExistingProjectCompatibility.cs",
795
+ "destination": "tools/project-forge-auth-kit/src/ProjectForge.Auth/PwaExistingProjectCompatibility.cs",
796
+ "sha256": "5ab5b6b4f017b8eed3d1d412966420eba07ed102b4fb41ecffd012ed77812d2c"
797
+ },
798
+ {
799
+ "source": "src/ProjectForge.Auth.Runtime/ProjectAuthStore.cs",
800
+ "destination": "tools/project-forge-auth-kit/src/ProjectForge.Auth.Runtime/ProjectAuthStore.cs",
801
+ "sha256": "e8255f660c6834803effc5659ca8c7d8517f34c195d371c811b77dee3ecd9c23"
802
+ },
803
+ {
804
+ "source": "src/ProjectForge.Auth.Runtime/ProjectForge.Auth.Runtime.csproj",
805
+ "destination": "tools/project-forge-auth-kit/src/ProjectForge.Auth.Runtime/ProjectForge.Auth.Runtime.csproj",
806
+ "sha256": "ae704a37487250b5198cc5c61bfcb600fe03b877d6b818c9700b8768d14365bc"
807
+ },
808
+ {
809
+ "source": "src/ProjectForge.Auth.Runtime/ProjectForgeAuthRuntimeExtensions.cs",
810
+ "destination": "tools/project-forge-auth-kit/src/ProjectForge.Auth.Runtime/ProjectForgeAuthRuntimeExtensions.cs",
811
+ "sha256": "73a73a3aca995030c3e2db90b8670e2998df348266bff196b3686751ce0325f9"
812
+ },
813
+ {
814
+ "source": "tools/household_connect.py",
815
+ "destination": "tools/project-forge-auth-kit/household_connect.py",
816
+ "sha256": "f3517f483fc027f9db3cfb3256000bf9738509cbeb17711fa3fbf93dd91df7be"
817
+ },
818
+ {
819
+ "source": "docs/HOUSEHOLD_REGISTRY.md",
820
+ "destination": "tools/project-forge-auth-kit/docs/HOUSEHOLD_REGISTRY.md",
821
+ "sha256": "17a466927402398f5b69a958325aaaaf5ea953193b8d3c60d5d5bf201dca8009"
822
+ },
823
+ {
824
+ "source": "docs/HOUSEHOLD_INFRASTRUCTURE_ADOPTION.md",
825
+ "destination": "tools/project-forge-auth-kit/docs/HOUSEHOLD_INFRASTRUCTURE_ADOPTION.md",
826
+ "sha256": "5cbbc6843d4a02b8f50153b6cc9cbaad9df6629748d8a541524af31bc9b07bdf"
827
+ },
828
+ {
829
+ "source": "src/ProjectForge.Transport/ProjectForge.Transport.csproj",
830
+ "destination": "tools/project-forge-auth-kit/src/ProjectForge.Transport/ProjectForge.Transport.csproj",
831
+ "sha256": "213e625c19e95b2a5ef315f01e2ca63fa56d3f5d1634dce920d493aeec1a4240"
832
+ },
833
+ {
834
+ "source": "src/ProjectForge.Transport/StrictPublicOriginTransport.cs",
835
+ "destination": "tools/project-forge-auth-kit/src/ProjectForge.Transport/StrictPublicOriginTransport.cs",
836
+ "sha256": "3c91970ad8f7f4feb0db8eda2289109a7748d439d71752a13a1c8e394b8070fd"
837
+ },
838
+ {
839
+ "source": "samples/ProjectForgeAuth.Api/Program.cs",
840
+ "destination": "tools/project-forge-auth-kit/samples/ProjectForgeAuth.Api/Program.cs",
841
+ "sha256": "baf0471b649864aeb7bdad76a74fe91279cc0a6b708b4487f62b2b940f6334cf"
842
+ },
843
+ {
844
+ "source": "samples/ProjectForgeAuth.Api/ProjectForgeAuth.Api.csproj",
845
+ "destination": "tools/project-forge-auth-kit/samples/ProjectForgeAuth.Api/ProjectForgeAuth.Api.csproj",
846
+ "sha256": "5f77a342dbfd3f062705f77f42285d41ee7ded2122533045d54de8d66cacb93b"
847
+ },
848
+ {
849
+ "source": "samples/ProjectForgeAuth.Api/wwwroot/index.html",
850
+ "destination": "tools/project-forge-auth-kit/samples/ProjectForgeAuth.Api/wwwroot/index.html",
851
+ "sha256": "97f28fd0a4269ecbff7cc2ac94d12fdcad236b0f5bd07c538e7d241f4796dfb0"
852
+ },
853
+ {
854
+ "source": "samples/ProjectForgeAuth.Api/wwwroot/auth.js",
855
+ "destination": "tools/project-forge-auth-kit/samples/ProjectForgeAuth.Api/wwwroot/auth.js",
856
+ "sha256": "cfb0d94c2c4b9bb2093dc9e322c226a8e4eead0a8cef31f194ddbac5d3420823"
857
+ },
858
+ {
859
+ "source": "src/ProjectForge.Auth.Runtime/ResendProjectInvitationDelivery.cs",
860
+ "destination": "tools/project-forge-auth-kit/src/ProjectForge.Auth.Runtime/ResendProjectInvitationDelivery.cs",
861
+ "sha256": "ff6a62f99eb984388c265aa72f47f3b223a3594371f8f041208f5f4e4887f017"
862
+ },
863
+ {
864
+ "source": "docs/PROJECT_AUTH_REFERENCE.md",
865
+ "destination": "tools/project-forge-auth-kit/docs/PROJECT_AUTH_REFERENCE.md",
866
+ "sha256": "1bbca014b897f3748e8af9039ba39cf14e8c445cf4748b2596b5ee733876a90b"
867
+ },
868
+ {
869
+ "source": "tools/project_auth_recovery.py",
870
+ "destination": "tools/project-forge-auth-kit/project_auth_recovery.py",
871
+ "sha256": "4f7864e33a1bc8bbb577a3459e06ed2fcf7e65a8349513c17510917680e68c9e"
872
+ },
873
+ {
874
+ "source": "docs/AUTH_RECOVERY_RETENTION.md",
875
+ "destination": "tools/project-forge-auth-kit/docs/AUTH_RECOVERY_RETENTION.md",
876
+ "sha256": "bc75171c05fdc3f454c67dcc3e2b7e226946ac32b5d02de62c9fc3b0b3bef0cf"
877
+ }
878
+ ]
733
879
  }
734
880
  }
735
881
  }
@@ -0,0 +1,101 @@
1
+ # Auth Kit recovery-set inventory and retention contract
2
+
3
+ This is an internal low-level Project recovery building block, not an
4
+ operator-ready Production backup command and **not** application recovery
5
+ qualification. The canonical generated Auth Kit helper is
6
+ `tools/project-forge-auth-kit/project_auth_recovery.py`.
7
+
8
+ ## Inert backup set
9
+
10
+ The owner must first independently quiesce its *exact* application instance,
11
+ prove a stable stopped Docker container/image and stable private storage, and
12
+ checkpoint any SQLite WAL. The `capture_quiesced_set(source, destination,
13
+ declaration, runtime_guard)` helper cannot stop the app or prove that the
14
+ provided runtime guard is trustworthy.
15
+
16
+ Use a new private backup-set parent (0700 on POSIX), outside the live data
17
+ tree. Each set name must have form `YYYYMMDDTHHMMSSZ-<8..32 lowercase hex>`
18
+ with a valid UTC calendar timestamp and collision-resistant suffix. The set
19
+ contains exactly `auth.sqlite`, `keys/key-*.xml`, and `receipt.json`.
20
+ Capture publishes exclusively; an existing destination, even an empty one
21
+ created after the initial check, is never replaced. A failed capture cleans
22
+ only its newly created private staging directory.
23
+
24
+ The receipt contains only Project identifier, recovery policy, protector
25
+ generation and file digests. It is **not signed** and cannot establish
26
+ provenance against a privileged attacker capable of rewriting both data
27
+ and receipt. Provider credentials and application secrets are not stored
28
+ in it.
29
+
30
+ ## Read-only inventory and retention
31
+
32
+ `plan_verified_retention(sets_root, declaration, keep_last=7)` inspects
33
+ every set before recommending **candidate names only**. It requires owner
34
+ private inventory storage, exact canonical names, valid receipts, complete
35
+ SQLite/key material, SHA-256 integrity, compatible Auth Kit schema and the
36
+ same declared Project, generation and authentication policy. Any
37
+ interrupted staging directory, symlink, missing/corrupt member, unknown
38
+ directory, or unexpected Project identity blocks the entire plan.
39
+
40
+ The function preserves at least two newest verified sets and at most
41
+ 1,000 tracked sets; the **plan never deletes anything**. No background job,
42
+ notification, or email receipt is installed by this function.
43
+
44
+ ## Explicit disposable-only deletion primitive (not a scheduled operation)
45
+
46
+ `apply_reviewed_disposable_retention(sets_root, lock_directory, declaration,
47
+ reviewed_plan, keep_last=7, environment="disposable",
48
+ authorize_disposable_delete=True)` is a Linux-only, low-level primitive.
49
+ It rejects every non-disposable environment, implicit authorization, unsafe
50
+ private directories, symlinks, corrupt sets, and inventory changes since the
51
+ complete **read-only plan** was reviewed. It preserves at least the latest two
52
+ verified recovery sets, bounds deletions to 32 per invocation, uses a private
53
+ nonblocking retention lock, and anchors unlink/rmdir operations to no-follow
54
+ directory descriptors instead of invoking recursive path deletion. After
55
+ each batch the operator must obtain and review a new read-only plan.
56
+
57
+ **This is not automatically called by capture or setup**, and is not a
58
+ Production retention CLI. The caller is responsible for proving the exact
59
+ generated Project's ownership, separation from live data, completed backup,
60
+ and the intended disposable environment. A directory deletion spanning
61
+ multiple files is **not atomic**; on a filesystem failure, a partially
62
+ pruned old candidate may need manual inspection/reconciliation before
63
+ any further retention or backup inventory planning. Do not treat a
64
+ partial failure as retention success.
65
+
66
+ Deployment Kit must still add and qualify a separately reviewed operator
67
+ driver coordinating the backup/recovery lifecycle lock, durable status and
68
+ receipts, persistent scheduling, recovery-point verification, escalation and
69
+ notification before automatic retention is permissible.
70
+
71
+ ## Restore is isolated and not readiness
72
+
73
+ `materialize_disposable_restore` creates only a new separate directory
74
+ outside backup and live paths and rechecks the hashes. No live data
75
+ overwrite or application restart occurs. A separate disposable application
76
+ must demonstrate actual key-ring protector viability, session-preservation
77
+ or invalidation policy, provider configuration recovery and health before
78
+ `applicationRecoveryReady` can ever be true. Artifact inspection always
79
+ reports it as **false**.
80
+
81
+ ## Running application health is not recovery readiness
82
+
83
+ The managed .NET Auth Kit maps `GET /health` **behind the exact trusted
84
+ Caddy peer/public-origin middleware**, not on an unprotected direct socket.
85
+ It performs a read-only check that the current per-Project Auth SQLite file
86
+ already exists with supported schema and expected account/session tables.
87
+ Missing, uninitialized or corrupt SQLite returns HTTP 503 without creating
88
+ a replacement identity database. An ordinary running instance with a
89
+ readable store returns 200. The endpoint exposes no account details,
90
+ filesystem paths, providers or session tokens.
91
+
92
+ This is an **application liveness signal only**, not a recovery completion
93
+ gate: even 200 does not prove that the restored DataProtection keyring
94
+ can decrypt historical state, that a prior session still authorizes,
95
+ that real Google/Resend work, that the backup set was preserved, that a
96
+ scheduled job/alert reached its target, or that normal routed traffic
97
+ is safe. All those remain independently proven acceptance requirements.
98
+
99
+ This material is not permission to change any PWA Production service, use
100
+ a household Provider credential, release the Authentication component to
101
+ npm, or label a generated Project ready for Production.
@@ -0,0 +1,55 @@
1
+ # One-time Household Infrastructure Adoption — PWA Pilot
2
+
3
+ **Status (2026-11-14):** Household Registry and Project Auth runtime source PRs are merged. The PWA-specific Auth Kit cutover was independently accepted in [PWA issue #43](https://github.com/mriekki/family-meal-planner-mark-pwa/issues/43), using a *fresh app-owned Auth Kit account/session database and persistent keyring* because the PWA was an unused shell; its prior records and credentials were preserved separately for rollback. **This does not qualify shared household provider resources, general generated-Project recovery or a published Authentication-enabled Project Forge package.** Auth Kit [release issue #5](https://github.com/mriekki/project-forge-auth-kit/issues/5) is the current release gate, and [recovery issue #9](https://github.com/mriekki/project-forge-auth-kit/issues/9) tracks generated-Project readiness. No provider/Production changes are authorized by this document.
4
+
5
+ This is the agreed one-time path for reusing relevant Family Meal Planner PWA infrastructure to bootstrap shared Project Forge household defaults. Do not repeat Google, Cloudflare, and Resend setup from scratch merely because a reusable household profile is introduced.
6
+
7
+ The Authentication Kit owns this provider-resource assessment proposal. Existing-repository adoption, New Project discovery, deployment mechanics, and the PWA's own work-item approvals remain owned by their established Project Forge components.
8
+
9
+ ## Separate infrastructure and application responsibilities
10
+
11
+ **A — Existing household infrastructure (first):** Inventory the existing PWA Google Cloud configuration, Cloudflare account/domain/Tunnel/Caddy network, Resend sending domain, and Ubuntu/Docker environment. Determine shared administrator-owned assets versus application-specific resources. Use independent **read-only provider verification** to establish current resource existence and ownership. Reuse appropriate shared assets, but allocate separate OAuth clients/redirects, narrowly scoped Resend keys, and exact app-owned ingress/routes for future Projects. PWA's credentials, hostnames, database, production route and runtime stay as-is.
12
+
13
+ **B — PWA-specific application adoption (accepted):** This migration has already occurred under PWA's separate review and Production approval. The accepted exception uses a newly seeded, isolated Auth Kit account/session database and persistent DataProtection keyring; historical PWA identities/passwords/sessions were *not* automatically translated because no active Product users or substantive Production user data needed migration. Historical PWA data and implementation were preserved for an explicit rollback. A future consumer with established users **must not inherit** this fresh-account exception: preserve or explicitly reconcile identities, sessions, passwords and app-owned authorization through that Project's own reviewed migration. Never auto-link by email or treat Household Registry membership as Project login.
14
+
15
+ ## How to run the implemented assessment
16
+
17
+ From the private Authentication Kit checkout, run:
18
+
19
+ python3 tools/household_adoption.py assess \
20
+ --checkout /path/to/fmp \
21
+ --expected-commit <exact-40-hex-pwa-head> \
22
+ --output /private/existing/directory/pwa-household-assessment.json
23
+
24
+ The parent output directory must already exist outside the PWA checkout. On Windows, use paths and continuation syntax appropriate for PowerShell. The output is a new JSON report: an existing report is NEVER overwritten. POSIX writes use owner-only mode; Windows permissions depend on the destination directory.
25
+
26
+ The report is bound to the exact local Git HEAD commit, with hashes for a **fixed allowlist of committed source files**: Project Forge baseline, deployment topology, OIDC implementation, account schema/service/endpoints, Resend adapter, production provider boundaries, and exact origin transport. It never reads uncommitted files, database content, .env, provider credential material, private SSH data, local settings or live provider accounts. No provider API calls or Production actions occur.
27
+
28
+ The report includes **source-evidence-found** versus **needs-source-review** markers, and all provider candidates remain **provider-inventory-unverified**. Source evidence cannot establish whether live Google, Cloudflare or Resend resources are currently provisioned, reusable or owned by the household. It contains a deterministic assessment SHA-256 for future reviewed plans and no secrets or personal account records.
29
+
30
+ The source assessor implements **assess** only and never applies changes. Separate
31
+ draft Registry tools now implement the GET-only provider ID verifier and
32
+ name-based discovery, the digest-locked adoption-plan generator, and an
33
+ explicitly reviewed host-local Registry resource import/rollback.
34
+ See `HOUSEHOLD_REGISTRY.md` for the exact operator procedure.
35
+ These are not a live provider migration or automatic PWA identity cutover.
36
+
37
+ ## Required review stages before any migration writes
38
+
39
+ 1. Assess the **delivered** PWA commit using the read-only tool, not an older chat summary or uncommitted worktree.
40
+ 2. Independently inventory non-secret Google Cloud, Cloudflare, and Resend resources using
41
+ the trusted local read-only verification **commands**. Cloudflare allows read-scoped
42
+ API tokens, whereas Resend may require a Full access key to perform its GET
43
+ domain metadata reads; this verifier issues GET requests only. Never load or
44
+ transmit such keys from ChatGPT or hosted CI. Confirm ownership, existing
45
+ sharing, restrictions and per-Project scope.
46
+ 3. Produce a reviewed household profile plan that classifies each asset as preserve, reuse existing shared resource, create separately scoped Project resource, or defer.
47
+ 4. Separately approve any actual provider or host write with an idempotent, least-privileged executor, durable receipt and rollback/stop controls. No automatic PWA route or credential replacement.
48
+ 5. Treat the accepted PWA Auth Kit cutover as a Project-specific completed event documented in PWA issue #43. For a different established Project, separately approve identity/session migration, disposable-data rehearsal, rollback, authorization and app-specific feature equivalence; do not assume the PWA fresh-account exception applies.
49
+ 6. Only enable an opt-in household capability in Bootstrap's existing New Project lifecycle after full generated-Project integration proof.
50
+
51
+ ## Current limitations and safety
52
+
53
+ Merged Auth Kit runtime includes .NET 10 Google OIDC code flow, per-Project SQLite account/invitation/session persistence, reference browser, exact proxy ingress checks, and a Bootstrap-managed optional generated API. Reusable, disposable Auth Kit recovery tests now cover real SQLite/DataProtection artifacts and isolated application HTTP startup; Deployment Kit includes exact Docker stopped-container capture, disposable stop/capture/resume and a durable interrupted-run journal. These are **not** real provider inventory, per-Project provider setup, durable Production scheduling/alert delivery, real full-application recovery readiness or npm release acceptance. The PWA-specific Production cutover is accepted separately; nothing here authorizes changing it.
54
+
55
+ Do not commit resulting reports to public repositories. They exclude credentials but still contain useful operational metadata. Secrets stay in protected local/provider input mechanisms, not Git, log output, shell argv, Chat, or plain JSON. PWA Production, FB-001/FB-002 requirements, provider resources and live data are all explicitly outside this change.
@@ -0,0 +1,246 @@
1
+ # Project Forge Household Registry
2
+
3
+ Status: **tested Registry and adoption engine under review, not deployed or published as a supported capability**.
4
+
5
+ This is a .NET 10 / SQLite service that persists shared household membership,
6
+ registered Projects and non-secret infrastructure references. It can be
7
+ accessed from a browser on Windows, WSL/Linux or macOS by a family Developer
8
+ such as Kyle using his own Google identity.
9
+
10
+ ## Ownership and authority
11
+
12
+ The Registry stores household identity, approved Google issuer+subject
13
+ membership, admin/developer/member roles, one-time invitations, registered
14
+ Projects, shared resource references, explicit Project-resource bindings
15
+ and audit entries. It does **not** store provider tokens, Project passwords,
16
+ application user accounts, PWA sessions, Google OAuth client secrets,
17
+ Resend keys, SSH credentials or application data.
18
+
19
+ A household Developer is not automatically an administrator or user of PWA
20
+ or any other Project. Independent applications continue to operate if the
21
+ Registry is temporarily offline.
22
+
23
+ ## First-time setup (trusted host; not a public endpoint)
24
+
25
+ Provision a private SQLite data directory owned by the non-root service UID.
26
+ For deployment, provide these settings from protected local configuration:
27
+
28
+ PF_HOUSEHOLD_DB=/data/household.sqlite
29
+ PF_HOUSEHOLD_ID=my-household
30
+ PF_HOUSEHOLD_NAME=My Household
31
+ PF_INITIAL_GOOGLE_SUBJECT=<exact-preverified-google-subject>
32
+
33
+ In a trusted host administration session execute exactly once:
34
+
35
+ dotnet ProjectForge.HouseholdRegistry.dll admin-init
36
+
37
+ The exact initial subject must be independently verified; email matches or
38
+ first-visitor behavior never grant administration. The operation refuses an
39
+ already-initialized registry.
40
+
41
+ The runtime requires:
42
+
43
+ PF_HOUSEHOLD_DB=/data/household.sqlite
44
+ PF_GOOGLE_CLIENT_ID=<per-registry-oauth-client-id>
45
+ PF_GOOGLE_CLIENT_SECRET_FILE=/run/secrets/google_oidc_client_secret
46
+ PF_REGISTRY_KEY_DIR=/data/keys
47
+ PF_REGISTRY_PUBLIC_ORIGIN=https://registry.example.com
48
+ PF_TRUSTED_CADDY_IP=<exact-reviewed-managed-caddy-ip>
49
+
50
+ Google callback: https://registry.example.com/signin-google
51
+
52
+ Do not put credentials in a tracked compose file, Git, Chat, CLI arguments,
53
+ shell history, or a public household profile. The service Dockerfile runs
54
+ as non-root on internal port 8080. The Deployment Kit must own Caddy/Tunnel
55
+ routing, exact public origin, allowed header forwarding and peer convergence;
56
+ do not publish the container directly to LAN/public TCP or trust Docker
57
+ network subnets. Use the Kit's read-only platform peer inspection before
58
+ reviewing the exact Caddy transport setting. The application rejects a
59
+ non-HTTPS or unexpected public host.
60
+
61
+ The `deploy/household-registry.compose.yml` reference is an isolated
62
+ no-host-port, non-root service with a separately mounted Google OAuth secret
63
+ file, durable SQLite volume and persistent private DataProtection key ring.
64
+ Set `PF_CADDY_NETWORK`, `PF_REVIEWED_CADDY_IP`,
65
+ `PF_HOUSEHOLD_DATA_DIR`, `PF_REGISTRY_GOOGLE_CLIENT_ID`,
66
+ `PF_GOOGLE_CLIENT_SECRET_FILE`, and `PF_REGISTRY_PUBLIC_ORIGIN`
67
+ through trusted host setup. The exact Caddy network/IP values must come
68
+ from the existing Deployment Kit 4.26.1 read-only proxy-peer observation.
69
+ Do not enable or rewrite the shared Caddy route from this Compose template,
70
+ and do not put OAuth secrets into provider inventory JSON.
71
+
72
+ On POSIX the key ring directory is restricted to owner access. Protect the
73
+ mounted persistent volume, backups and host secret file. A Production
74
+ deployment must validate process UID/GID and backup/restore before cutover.
75
+
76
+ The .NET runtime uses a Google OIDC code flow with PKCE and server cookie;
77
+ the browser sends an antiforgery header on all mutations. Every Registry API
78
+ call rereads member status so disabling a Developer immediately blocks future
79
+ Registry API access. Provider references always start unverified unless
80
+ installed through a separately approved adoption plan.
81
+
82
+ ## One-time existing household adoption from PWA
83
+
84
+ First create the exact-commit read-only PWA source assessment, as documented
85
+ in HOUSEHOLD_INFRASTRUCTURE_ADOPTION.md:
86
+
87
+ python3 tools/household_adoption.py assess --checkout /path/to/fmp \
88
+ --expected-commit <exact-40-hex-pwa-commit> \
89
+ --output /private/evidence/pwa.json
90
+
91
+ Verify existing shared provider resources with read-only scoped credentials,
92
+ in a trusted local terminal. The Cloudflare and Resend credentials are read
93
+ from PF_CF_READ_TOKEN and PF_RESEND_READ_TOKEN environment variables; Google
94
+ Cloud access uses the current trusted gcloud auth identity.
95
+
96
+ python3 tools/household_provider_verify.py verify \
97
+ --google-project <google-project-id> \
98
+ --cf-account <account-id> --cf-zone <zone-id> \
99
+ --cf-tunnel <tunnel-id> --resend-domain <domain-id> \
100
+ --output /private/evidence/providers.json
101
+
102
+ **Preferred when existing provider names are known but IDs are not:** locate
103
+ the exact existing Cloudflare zone/tunnel and Resend sender domain automatically
104
+ through their read-only listing endpoints, then independently verify all
105
+ discovered IDs using the original detailed provider API checks:
106
+
107
+ python3 tools/household_provider_discover.py discover \
108
+ --google-project <existing-google-cloud-project-id> \
109
+ --cf-zone-name <exact-cloudflare-zone-domain> \
110
+ --cf-tunnel-name <exact-production-tunnel-name> \
111
+ --resend-domain-name <exact-verified-sender-domain> \
112
+ --pwa-hostname <exact-existing-pwa-public-hostname> \
113
+ --pwa-origin-service <exact-existing-pwa-unix-origin-service> \
114
+ --output /private/evidence/providers.json
115
+
116
+ The name-discovery command uses the same protected environment variables and
117
+ `gcloud auth` context as the ID-based `verify` command. It fails closed if a
118
+ list is incomplete or paginated, resource names are ambiguous or missing, or
119
+ any detail response disagrees with the expected resource. Its only persistent
120
+ output is the same private, exclusive-created, non-secret provider observation
121
+ JSON. When both optional `--pwa-hostname` and `--pwa-origin-service`
122
+ are provided, it also performs a **GET** for the remotely managed tunnel's
123
+ ingress configuration, requires exactly one matching existing PWA hostname
124
+ without a path restriction, checks the exact reviewed Unix socket service and
125
+ the owning Cloudflare account, and records only that matched route. A
126
+ missing/changed/ambiguous route fails closed. A locally managed tunnel or a
127
+ Cloudflare account without read permission for the configuration endpoint
128
+ cannot satisfy this optional proof and requires a separately reviewed
129
+ on-host ingress check; do not silently treat a tunnel record as routing
130
+ evidence. The verifier never rewrites the existing PWA route.
131
+
132
+ The route proof does not check the Caddy virtual-host table, Google OAuth
133
+ client redirects, or live application availability; those still have
134
+ independent acceptance requirements. It does not change tunnel routing or
135
+ attest Google OAuth client callbacks.
136
+
137
+ **Optional no-API-key-in-the-shell Resend path:** the official Resend CLI
138
+ supports an interactive, locally approved browser OAuth login. After separately
139
+ installing the official `resend` CLI, the operator may authorize an isolated
140
+ local profile (do **not** run this in CI):
141
+
142
+ resend --profile household-review login --web
143
+
144
+ Then append `--resend-cli --resend-profile household-review` to the
145
+ `household_provider_discover.py discover` command above. In that mode, the
146
+ script runs only `resend domains list` and `resend domains get <id>` with
147
+ JSON output, captures/sanitizes CLI output, and does not require
148
+ `PF_RESEND_READ_TOKEN`, pass a token argument or read the CLI credential file.
149
+ The CLI's locally stored OAuth permission may still be broad; only the
150
+ invoked commands are read-only. Resend's browser authorization callback must
151
+ reach the same local machine as the CLI (check WSL/Windows localhost routing
152
+ if needed). No OAuth login is launched or granted by the verification script.
153
+ Cloudflare still needs its separately scoped Zone/Tunnel Read token. For Cloudflare, use a token scoped to **Zone Read** and
154
+ **Cloudflare Tunnel/Connectors Read** on the intended account. **Resend
155
+ currently offers Full access or Sending access keys, not a read-only API-key
156
+ permission**; `GET /domains` requires an authorized key that can retrieve
157
+ domain metadata. The variable name `PF_RESEND_READ_TOKEN` describes the
158
+ **read-only behavior of this verifier**, not the Resend key's privileges.
159
+ If the operator chooses to provide a Full access key, use it **only in the
160
+ trusted local terminal**, never in hosted CI or a tracked file, and remove
161
+ its environment variable after the read. An existing send-only key cannot
162
+ be used for this domain inventory. No new key should be created without
163
+ explicit operator review. **Never paste tokens, key files or Google
164
+ authorization codes into a chat or commit them into Git.**
165
+
166
+ These commands make only HTTPS GET requests; no provider resource or
167
+ credential is modified. The output contains independently confirmed non-secret
168
+ references, status and observation time. Evidence must be less than 24 hours
169
+ old to create a new plan. Dashboard screenshots are useful inventory clues
170
+ but are **not** equivalent to this API-backed evidence.
171
+
172
+ Create the exact reviewed import plan:
173
+
174
+ python3 tools/household_adoption_plan.py plan \
175
+ --assessment /private/evidence/pwa.json \
176
+ --provider-evidence /private/evidence/providers.json \
177
+ --household-id my-household \
178
+ --registry-origin https://registry.example.com \
179
+ --pwa-hostname <exact-existing-pwa-public-hostname> \
180
+ --pwa-origin-service <exact-existing-pwa-unix-origin-service> \
181
+ --output /private/evidence/adoption-plan.json
182
+
183
+ Supplying the exact PWA hostname and Unix origin to the plan command
184
+ requires the upstream provider evidence to contain a matching, independently
185
+ read Cloudflare tunnel route. A missing or changed route cannot be used to
186
+ approve the household resource-reference import. This binds the preexisting
187
+ PWA ingress preservation review into the exact provider-evidence digest;
188
+ it does not test the downstream Caddy virtual-host mapping. The two route
189
+ parameters must be supplied together. If the tunnel is locally managed and
190
+ the configuration-read API is unavailable, stop and obtain a separately
191
+ reviewed local ingress proof rather than claiming the remote tunnel
192
+ configuration was verified.
193
+
194
+ Review the full plan and its printed file SHA-256. After administrator
195
+ approval, copy the reviewed plan securely to the Registry host, ensure
196
+ PF_INITIAL_GOOGLE_SUBJECT identifies the approved household admin, and
197
+ set PF_APPROVED_ADOPTION_SHA to that exact reviewed file checksum and
198
+ PF_HOUSEHOLD_ADOPTION_PLAN to the local private file. Then run, from the
199
+ trusted host context only:
200
+
201
+ dotnet ProjectForge.HouseholdRegistry.dll admin-import
202
+
203
+ This is the **actual one-time household resource reference migration**.
204
+ It creates five reviewed Google/Cloudflare/Resend reference records in one
205
+ SQLite transaction with an audit receipt, preserves existing matching
206
+ resources, and treats an identical exact plan replay as no-op. It does not
207
+ create new OAuth clients, Resend keys or DNS routes and does not modify PWA
208
+ or its Production data. A different plan must receive a fresh reviewed
209
+ checksum. An operator-reviewed resource reference is not a cryptographic
210
+ attestation of continuing provider availability.
211
+
212
+ ## Invite another Developer
213
+
214
+ The initial administrator signs in to the Registry browser with Google,
215
+ creates a one-time 48-hour invitation for the Developer's verified Google
216
+ email and shares the resulting token privately (not in a URL, Git issue or
217
+ terminal logs). Kyle signs into the same Registry origin using his Google
218
+ account and redeems that token. He can then register Projects and view
219
+ approved household references. He still needs separately approved provider
220
+ credentials and Project-specific application authorization.
221
+
222
+ The Registry address itself is a non-secret **explicit locator** shared with
223
+ the Developer. New Project Forge setup must verify that exact HTTPS origin
224
+ and perform authorized discovery; it must never scan household networks or
225
+ copy the Registry database.
226
+
227
+ ## Backups and safe recovery
228
+
229
+ Use a trusted SQLite consistent backup or stop the Registry before copying
230
+ its protected volume. Protect the directory and backups with owner-only
231
+ permissions; perform an integrity check and disposable restore rehearsal
232
+ before Production adoption. A schema version newer than the service
233
+ understands fails closed. Backup and restoration must not overwrite PWA's
234
+ independent SQLite database or application-managed migration journals.
235
+
236
+ ## What is still required for production and next kit
237
+
238
+ Registry deployment into the actual household, real provider credentials and
239
+ resource verification, controlled PWA authentication adapter/migration,
240
+ existing-account and password compatibility proof, full Google callback and
241
+ hostile Caddy/Tunnel tests, cross-platform New Project setup integration and
242
+ package distribution/installed consumer qualification.
243
+
244
+ These are real release blockers, not solved by the Registry's passing domain
245
+ tests. A successful Registry deployment is also not permission to rewrite
246
+ PWA Production, reset user sessions or install a new provider adapter.