Skip to content
Merged
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# C3 — Workspace dependency graph

> **Last validated:** 2026-06-08 by @Skords-01. **Next review:** 2026-09-06.
> **Last validated:** 2026-06-09 by @Skords-01. **Next review:** 2026-09-07.
> **Status:** Active

<!-- AUTO-GENERATED FILE. Do not edit by hand. Regenerate via `pnpm docs:gen-architecture-diagrams`. -->

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use the canonical auto-generated marker prefix.

This file is auto-generated, but the header does not start with the required <!-- AUTO-GENERATED --> marker form for docs generators/checks.

As per coding guidelines, “Auto-generated documentation must start with <!-- AUTO-GENERATED --> marker per Hard Rule #25”.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/02-engineering/architecture/diagrams/c3-workspaces.md` at line 6,
Replace the current header comment with the canonical auto-generated marker by
changing the top-of-file comment to exactly <!-- AUTO-GENERATED --> (preserve
any following explanatory line like "Do not edit by hand." if needed) so the
docs generator/checks recognize the file as auto-generated; after updating the
marker, regenerate the diagrams using the existing generation command noted in
the file comment (pnpm docs:gen-architecture-diagrams) to ensure the file
matches expected tooling output.

Source: Coding guidelines

Expand Down
9 changes: 6 additions & 3 deletions docs/04-governance/adr/0003-refund-and-dispute-handling.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
- **Supersedes:** —
- **Related:**
- [`docs/04-governance/adr/0001-monetization-architecture.md`](./0001-monetization-architecture.md) — ADR-1.1 (Stripe primary), ADR-1.8 (webhook event-id retention), ADR-1.11 (cancel-at-period-end).
- [`docs/01-product/launch/business/01-monetization-and-pricing.md`](../../01-product/launch/business/01-monetization-and-pricing.md) — тіри і ціни (Pro ₴99/міс, ₴799/рік).
- [`docs/01-product/launch/business/01-monetization-and-pricing.md`](../../01-product/launch/business/01-monetization-and-pricing.md) — тіри і ціни (Pro $7/міс, $49/рік). <!-- price updated 2026-06-09: ₴99/міс → $7/міс per ADR-0051 -->

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Switch inline HTML notes to the required AI marker format.

The added <!-- price updated ... --> comments should use the mandated marker taxonomy (e.g., AI-NOTE / AI-CONTEXT) for markdown files.

As per coding guidelines, “**/*.{ts,tsx,js,jsx,md}: Use AI markers in code: AI-NOTE, AI-CONTEXT, AI-DANGER, ...”.

Also applies to: 25-27, 305-305

🧰 Tools
🪛 LanguageTool

[style] ~10-~10: Після голосної зі звуком і/и варто писати й або та: тіри «й» ціни, тіри «та» ціни
Context: .../01-monetization-and-pricing.md) — тіри і ціни (Pro $7/міс, $49/рік). <!-- price ...

(EUPHONY_CONJ_I_Y)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/04-governance/adr/0003-refund-and-dispute-handling.md` at line 10,
Replace the inline HTML comment <!-- price updated 2026-06-09: ₴99/міс → $7/міс
per ADR-0051 --> with the mandated AI marker syntax (e.g., AI-NOTE) directly
adjacent to the markdown line that references the pricing doc (the line
containing [`docs/01-product/launch/business/01-monetization-and-pricing.md`]);
do the same for the other occurrences noted (around lines 25–27 and line 305) so
each inline HTML note becomes something like: <!-- AI-NOTE: price updated
2026-06-09: ₴99/міс → $7/міс per ADR-0051 --> ensuring the marker token (AI-NOTE
/ AI-CONTEXT / AI-DANGER as appropriate) replaces the raw HTML comment while
preserving the original message text.

Source: Coding guidelines


⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Ukrainian euphony typo in the updated phrase.

“тіри і ціни” should be adjusted to the euphonically correct conjunction form.

🧰 Tools
🪛 LanguageTool

[style] ~10-~10: Після голосної зі звуком і/и варто писати й або та: тіри «й» ціни, тіри «та» ціни
Context: .../01-monetization-and-pricing.md) — тіри і ціни (Pro $7/міс, $49/рік). <!-- price ...

(EUPHONY_CONJ_I_Y)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/04-governance/adr/0003-refund-and-dispute-handling.md` at line 10, Fix
the Ukrainian typo by replacing the incorrect phrase "тіри і ціни" in the ADR
text with the correct wording "тарифи і ціни" (or "тарифи та ціни" if you prefer
the conjunction for euphony); update the markdown line that currently contains
[`docs/01-product/launch/business/01-monetization-and-pricing.md`] — тіри і ціни
(Pro $7/міс, $49/рік) so it reads the corrected phrase while keeping the link
and pricing note intact.

Source: Linters/SAST tools

- [`docs/01-product/launch/business/06-monetization-architecture.md`](../../01-product/launch/business/06-monetization-architecture.md) — risk register #8 («нічого про refund / proration»).

---
Expand All @@ -22,8 +22,9 @@ ADR-0001 явно винісь refund / dispute flow в окремий ADR (ди
app — Pro у юзера лишається активним, навіть якщо платіж вже відкликано; (б)
без явної політики повернень ми порушуємо вимоги Stripe Standard Acceptable
Use Policy (refund policy має бути доступна юзеру **до** оплати); (в) при ціні
Pro ₴99/міс (~$2.30) комісія Stripe за один dispute (€15) повністю з'їдає
6 місяців оплати — політика чисто-grace для disputes економічно нестійка.
Pro $7/міс <!-- price updated 2026-06-09: ₴99/міс → $7/міс per ADR-0051 -->
комісія Stripe за один dispute (€15) повністю з'їдає кілька місяців оплати —
політика чисто-grace для disputes економічно нестійка.

| Сценарій | Тригер | Дія |
| ------------------------------ | ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
Expand Down Expand Up @@ -300,3 +301,5 @@ accepted.
balance — можна замість refund-у нарахувати credit на наступний цикл.
Менше тертя для returning-юзерів. Reopen якщо побачимо ≥30% refund-ів
"хочу скасувати, але повернуся" (типовий churn-recovery кейс).

<!-- price updated 2026-06-09: ₴99/міс → $7/міс per ADR-0051 -->
5 changes: 5 additions & 0 deletions docs/04-governance/adr/0062-openapi-source-of-truth.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ deferred, trigger-gated на перший production contract-bug:
- Contract roundtrip tests (`tests/contract/openapi-roundtrip.test.ts`).
- Schemathesis property-based testing (`.github/workflows/contract-tests.yml`).

## Related ADRs

- **[ADR-0025](./0025-openapi-generation.md)** — Introduced the `zod-to-openapi` infrastructure, generator script (`generate-openapi.mjs`), and CI freshness check that this ADR formalises as the canonical source-of-truth decision. ADR-0062 confirms and extends ADR-0025; it does not supersede it.
- **[ADR-0053](./0053-api-versioning-policy.md)** — API versioning policy (`/api/v1/*` canonical URL scheme) that the OpenAPI spec documents.

## Links

- PR-23 spec: [`docs/90-work/initiatives/stack-pulse-2026-05/pr-23-openapi-contract-tests.md`](../../90-work/initiatives/stack-pulse-2026-05/pr-23-openapi-contract-tests.md)
Expand Down
19 changes: 11 additions & 8 deletions packages/shared/src/utils/date.property.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function arbitraryLocalDate(): Date {
const year = 1970 + Math.floor(rng() * 130);
const month = Math.floor(rng() * 12); // 0..11
const day = 1 + Math.floor(rng() * 28); // 1..28, always valid
const hour = Math.floor(rng() * 24);
const hour = Math.floor(rng() * 21); // 0..20: UTC 21–23 crosses to next Kyiv day

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Make test data timezone-explicit (UTC) to avoid env-dependent Kyiv-day flakes.

These changes still use local-time Date construction while the comments/reasoning assume UTC. On non-UTC runners, toLocalISODate (Kyiv) can drift by a day and make this suite flaky.

Suggested fix
 function arbitraryLocalDate(): Date {
   const year = 1970 + Math.floor(rng() * 130);
   const month = Math.floor(rng() * 12); // 0..11
   const day = 1 + Math.floor(rng() * 28); // 1..28, always valid
-  const hour = Math.floor(rng() * 21); // 0..20: UTC 21–23 crosses to next Kyiv day
+  const hour = Math.floor(rng() * 21); // 0..20 UTC: avoids Kyiv next-day rollover
   const minute = Math.floor(rng() * 60);
-  return new Date(year, month, day, hour, minute);
+  return new Date(Date.UTC(year, month, day, hour, minute));
 }
 ...
-      const earlyMorning = new Date(
-        base.getFullYear(),
-        base.getMonth(),
-        base.getDate(),
+      const earlyMorning = new Date(Date.UTC(
+        base.getUTCFullYear(),
+        base.getUTCMonth(),
+        base.getUTCDate(),
         6,
         0,
         0,
-      );
-      const lateEvening = new Date(
-        base.getFullYear(),
-        base.getMonth(),
-        base.getDate(),
+      ));
+      const lateEvening = new Date(Date.UTC(
+        base.getUTCFullYear(),
+        base.getUTCMonth(),
+        base.getUTCDate(),
         20,
         0,
         0,
-      );
+      ));

Also applies to: 67-69, 72-88

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/shared/src/utils/date.property.test.ts` at line 34, The tests build
Date instances using local-time constructors while the assertions assume UTC
(Kyiv) semantics, causing day-boundary flakes; update the test date creation to
be timezone-explicit by using UTC-based constructors or Date.UTC (or
setUTCFullYear/setUTCHours) so the random hour (hour variable used for 0..20)
and all other test dates are created in UTC; apply the same change to other
spots referenced (where toLocalISODate is exercised and the date fixtures around
lines 67–88) so all test data is timezone-explicit and no longer
environment-dependent.

Source: Coding guidelines

const minute = Math.floor(rng() * 60);
return new Date(year, month, day, hour, minute);
}
Expand Down Expand Up @@ -64,25 +64,28 @@ describe("shared/utils/date – toLocalISODate property", () => {
});

it("is time-of-day invariant: only the calendar day matters", () => {
// Use 06:00 and 20:00 UTC — both safely within the same Kyiv calendar day
// regardless of DST offset (UTC+2 winter: 08:00/22:00; UTC+3 summer: 09:00/23:00).
// UTC 21–23 would cross Kyiv midnight, so we avoid those hours here.
for (let i = 0; i < NUM_RUNS; i++) {
const base = arbitraryLocalDate();
const midnight = new Date(
const earlyMorning = new Date(
base.getFullYear(),
base.getMonth(),
base.getDate(),
0,
6,
0,
0,
);
const lateNight = new Date(
const lateEvening = new Date(
base.getFullYear(),
base.getMonth(),
base.getDate(),
23,
59,
59,
20,
0,
0,
);
expect(toLocalISODate(lateNight)).toBe(toLocalISODate(midnight));
expect(toLocalISODate(lateEvening)).toBe(toLocalISODate(earlyMorning));
}
});

Expand Down
Loading