Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .github/workflows/pr-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,4 +75,8 @@ jobs:
run: php artisan migrate --force

- name: Run tests
run: php artisan test
# local-only group is excluded here on purpose: it guards a docs
# invariant (PLAN.md milestone Targets == milestones.ics end dates)
# that should fail the local suite, not gate every PR. See
# tests/Feature/Docs/MilestoneScheduleConsistencyTest.php.
run: php artisan test --exclude-group=local-only
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
- Always run artisan/composer/npm commands through Sail (`./vendor/bin/sail ...`).
- Doc-tree roles, canonical docs, strategy doc rules, checklist-depth separation, and CHANGELOG conventions all live in [`docs/DOC_ROLES.md`](docs/DOC_ROLES.md). Read it when navigating docs or deciding where new content belongs.
- Where dates are necessary in docs, use the date from the system you're running on.
- Milestone dates have one source of truth: `docs/milestones.ics`. PLAN.md's "Target" column must equal each milestone's `.ics` end date (DTEND). This invariant is guarded by `tests/Feature/Docs/MilestoneScheduleConsistencyTest.php`, tagged `#[Group('local-only')]` — it runs in the local suite (`./vendor/bin/sail artisan test`) but is excluded from the GitHub `PR Tests` gate, so run tests locally after any milestone-date change.
- When adding features, update or create migrations + tests, then run `./vendor/bin/sail artisan test`.
- Also keep AGENTS.md updated to save on churn from session switching.
- Keep `.cybercreek/` local-only and untracked; do not commit agent coordination logs, local recovery files, or other local-only helper artifacts. For local-only work under `.cybercreek/`, follow `.cybercreek/AGENTS_LOCAL.md` if present.
Expand Down
8 changes: 4 additions & 4 deletions docs/PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ Use [`docs/BACKLOG.md`](BACKLOG.md) for post-MVP work only.
- Most recently completed milestone doc: [`docs/milestones/x18_PRERELEASE_CONTENT_SEO.md`](milestones/x18_PRERELEASE_CONTENT_SEO.md)

## Published Release Target
- **First public release: mid-to-late 2027.** The RC milestone timeline (MS21, targeting 2026-07-05) covers the release candidate. The published release target accounts for post-RC work needed before an official first release.
- **First public release: mid-to-late 2027.** The RC milestone timeline (MS21, targeting 2026-08-11) covers the release candidate. The published release target accounts for post-RC work needed before an official first release.

## Active and Upcoming Milestones
| Status | ID | Milestone | Short intent | Target | Primary doc |
|---|---|---|---|---|---|
| [ ] | 19 | RC Hardening & Ops | RC hardening before mainnet cutover: notification coverage audit; auth/password policy hardening (419-to-login redirect, session expiry logout); LLC formation (entity, EIN, bank account, operating agreement); legal layer (ToS, Privacy Policy, disclaimers, monetization-neutral copy review, UI placement); content promises reconciliation; contributor docs refresh; 2FA (email baseline, opportunistic TOTP). | 2026-06-11 | [`docs/milestones/19_RC_HARDENING_OPS.md`](milestones/19_RC_HARDENING_OPS.md) |
| [ ] | 20 | Mainnet Cutover Preparation | Define and rehearse env flips, wallet validation, mail sanity checks, and backout steps for mainnet cutover. | 2026-06-23 | [`docs/milestones/20_MAINNET_CUTOVER_PREP.md`](milestones/20_MAINNET_CUTOVER_PREP.md) |
| [ ] | 21 | CryptoZing.app Deployment (RC) | Deploy the RC under `cryptozing.app`, replace the GitHub Pages placeholder at `/` with the live app landing page without breaking the SEO baseline established in MS15, remove temporary mail aliasing, and complete rollout verification. | 2026-07-05 | [`docs/milestones/21_RC_DEPLOYMENT.md`](milestones/21_RC_DEPLOYMENT.md) |
| [ ] | 19 | RC Hardening & Ops | RC hardening before mainnet cutover: notification coverage audit; auth/password policy hardening (419-to-login redirect, session expiry logout); LLC formation (entity, EIN, bank account, operating agreement); legal layer (ToS, Privacy Policy, disclaimers, monetization-neutral copy review, UI placement); content promises reconciliation; contributor docs refresh; 2FA (email baseline, opportunistic TOTP). | 2026-07-18 | [`docs/milestones/19_RC_HARDENING_OPS.md`](milestones/19_RC_HARDENING_OPS.md) |
| [ ] | 20 | Mainnet Cutover Preparation | Define and rehearse env flips, wallet validation, mail sanity checks, and backout steps for mainnet cutover. | 2026-07-30 | [`docs/milestones/20_MAINNET_CUTOVER_PREP.md`](milestones/20_MAINNET_CUTOVER_PREP.md) |
| [ ] | 21 | CryptoZing.app Deployment (RC) | Deploy the RC under `cryptozing.app`, replace the GitHub Pages placeholder at `/` with the live app landing page without breaking the SEO baseline established in MS15, remove temporary mail aliasing, and complete rollout verification. | 2026-08-11 | [`docs/milestones/21_RC_DEPLOYMENT.md`](milestones/21_RC_DEPLOYMENT.md) |

## Completed Milestones
| Status | ID | Milestone | Short intent | Primary doc |
Expand Down
10 changes: 5 additions & 5 deletions docs/milestones.ics
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ UID:cz-ms18@cryptozing.app
END:VEVENT
BEGIN:VEVENT
DTSTART;VALUE=DATE:20260508
DTEND;VALUE=DATE:20260704
DTEND;VALUE=DATE:20260718
SUMMARY:MS19 — RC Hardening & Ops
DESCRIPTION:RC hardening: notification coverage, auth/password policy, LLC formation, legal layer (ToS, Privacy Policy, disclaimers, monetization-neutral copy), content promises reconciliation, contributor docs, 2FA (email baseline, optional TOTP).
UID:cz-ms19@cryptozing.app
END:VEVENT
BEGIN:VEVENT
DTSTART;VALUE=DATE:20260703
DTEND;VALUE=DATE:20260716
DTSTART;VALUE=DATE:20260717
DTEND;VALUE=DATE:20260730
SUMMARY:MS20 — Mainnet Cutover Preparation
DESCRIPTION:Define and rehearse env flips, wallet validation, mail sanity, and backout steps for mainnet cutover.
UID:cz-ms20@cryptozing.app
END:VEVENT
BEGIN:VEVENT
DTSTART;VALUE=DATE:20260715
DTEND;VALUE=DATE:20260728
DTSTART;VALUE=DATE:20260729
DTEND;VALUE=DATE:20260811
SUMMARY:MS21 — CryptoZing.app Deployment (RC)
DESCRIPTION:Deploy the RC under cryptozing.app, replace GitHub Pages placeholder, remove temporary mail aliasing, complete rollout verification.
UID:cz-ms21@cryptozing.app
Expand Down
4 changes: 2 additions & 2 deletions docs/strategies/19.3_LLC_FORMATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ CyberCreek LLC is formed **first**, then CryptoZing LLC is formed **with CyberCr

Sequence is serial: CryptoZing's filing can't name CyberCreek as member until CyberCreek exists. Rough path — CyberCreek formation + ~3-week publication (filed 2026-06-08, ≈6/29), then CryptoZing formation + ~3-week publication (≈7/20).

**Timing watch-item, not a blocker:** MS21 (deploy-time entity-name swap) is 2026-07-15. The swap needs the CryptoZing LLC name *locked* — i.e., AZCC Articles approved, which lands early in CryptoZing's window — not the full 3-week publication, which may finish after MS21 as a compliance step. The parallel-track design insulates MS19 regardless: Phase 5 drafts ToS/Privacy with placeholder names, and nothing in MS19 close waits on formation. If AZCC is slow approving CryptoZing's Articles, reconsider then; don't pre-slip.
**Timing watch-item, not a blocker:** MS21 (deploy-time entity-name swap) is 2026-07-29 (moved out two weeks alongside the rest of MS19 to absorb LLC formation lead time). CryptoZing formation + ~3-week publication now lands ≈7/20, comfortably ahead of the swap. The swap needs the CryptoZing LLC name *locked* — i.e., AZCC Articles approved, which lands early in CryptoZing's window — not the full 3-week publication, which may finish after MS21 as a compliance step. The parallel-track design insulates MS19 regardless: Phase 5 drafts ToS/Privacy with placeholder names, and nothing in MS19 close waits on formation. If AZCC is slow approving CryptoZing's Articles, reconsider then; don't pre-slip.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Extend MS19 past the LLC completion date

This updated timing still puts the LLC phase completion after the new MS19 close: the strategy says the serial CyberCreek → CryptoZing formation/publication path lands around 2026-07-20, while the canonical calendar in this commit closes MS19 on 2026-07-18 and the phase exit criteria still require CryptoZing publication to be complete. In that schedule, MS19 can reach its planned close before this required phase can finish, so either the MS19 window needs to move past ≈7/20 or publication needs to be explicitly moved out of MS19 exit criteria.

Useful? React with 👍 / 👎.


## Decisions made

Expand Down Expand Up @@ -47,7 +47,7 @@ Sequence is serial: CryptoZing's filing can't name CyberCreek as member until Cy

## C. Business bank account

1. [ ] Confirm AFCU's business account requirements (formation receipt, EIN, deposit minimums, fee schedule, how they handle an LLC-owned-by-LLC).
1. [ ] Confirm AFCU's business account requirements (formation receipt, EIN, deposit minimums, fee schedule).
2. [ ] Open business checking in **CryptoZing LLC's** name + EIN.
3. [ ] Route all CryptoZing income/expenses through the business account exclusively. No mixing with personal funds — co-mingling is the most common way solo operators lose liability protection in court.

Expand Down
94 changes: 94 additions & 0 deletions tests/Feature/Docs/MilestoneScheduleConsistencyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
<?php

namespace Tests\Feature\Docs;

use Illuminate\Support\Str;
use PHPUnit\Framework\Attributes\Group;
use Tests\TestCase;

/**
* Guards the one invariant that kept drifting by hand: every milestone's
* Target in PLAN.md must equal that milestone's end date in milestones.ics.
*
* Tagged local-only and excluded from the GitHub `PR Tests` gate
* (see .github/workflows/pr-tests.yml). It runs as part of the normal local
* suite — `./vendor/bin/sail artisan test` — so drift surfaces the moment you
* edit one file without the other, without gating PRs on a docs invariant.
*/
#[Group('local-only')]
class MilestoneScheduleConsistencyTest extends TestCase
{
public function test_plan_targets_match_ics_end_dates(): void
{
$icsEndDates = $this->icsEndDatesByMilestone();
$planTargets = $this->planTargetsByMilestone();

$this->assertNotEmpty($planTargets, 'No milestone rows parsed from PLAN.md active table.');

foreach ($planTargets as $id => $target) {
$this->assertArrayHasKey(
$id,
$icsEndDates,
"PLAN.md lists MS{$id} but milestones.ics has no matching event."
);

$this->assertSame(
$icsEndDates[$id],
$target,
"MS{$id}: PLAN.md Target ({$target}) != milestones.ics end date ({$icsEndDates[$id]})."
);
}
}

/** @return array<int,string> milestone number => end date (Y-m-d) */
private function icsEndDatesByMilestone(): array
{
$ics = file_get_contents(base_path('docs/milestones.ics'));

preg_match_all('/BEGIN:VEVENT(.*?)END:VEVENT/s', $ics, $blocks);

$endDates = [];

foreach ($blocks[1] as $block) {
if (! preg_match('/SUMMARY:MS(\d+)/', $block, $summary)) {
continue;
}

if (! preg_match('/DTEND;VALUE=DATE:(\d{8})/', $block, $dtend)) {
continue;
}

$endDates[(int) $summary[1]] = \DateTime::createFromFormat('Ymd', $dtend[1])->format('Y-m-d');
}

return $endDates;
}

/** @return array<int,string> milestone number => Target date (Y-m-d) */
private function planTargetsByMilestone(): array
{
$plan = file_get_contents(base_path('docs/PLAN.md'));

$section = Str::between($plan, '## Active and Upcoming Milestones', '## Completed Milestones');

$targets = [];

foreach (preg_split('/\R/', $section) as $line) {
// Table rows look like: | [ ] | 19 | name | intent | 2026-07-18 | doc |
if (! preg_match('/^\|\s*\[.?\]\s*\|/', $line)) {
continue;
}

$cells = array_map('trim', explode('|', $line));

// explode leaves an empty leading cell; columns: 1=status 2=id ... 5=Target
if (! ctype_digit($cells[2] ?? '') || ! preg_match('/^\d{4}-\d{2}-\d{2}$/', $cells[5] ?? '')) {
continue;
}

$targets[(int) $cells[2]] = $cells[5];
}

return $targets;
}
}
Loading