Skip to content

Commit 1091cf6

Browse files
Pin v2 visibility metadata contract to typed-tables-only
`docs/search-attributes-architecture.md` and `docs/workflow-memos-architecture.md` now describe the v2 visibility metadata contract as one storage surface per typed table: `workflow_search_attributes` is the authoritative storage for search attributes and `workflow_memos` is the authoritative storage for memos. The legacy JSON columns on `workflow_runs.search_attributes` and `workflow_runs.memo` are named as transitional artifacts that must be removed before the v2.0 stable release. The previous "Phase 0/1/2/3/4 forward migration" ladder and the "For v2 alpha/beta releases" backwards-compatibility sections are removed. v2 has never been released, so different v2 development snapshots are not a mixed fleet and no v2-alpha-to-v2 backwards-compatibility contract is owed; operators upgrading from v1 follow the documented v1-to-v2 migration path. Each document now lists the required pre-v2.0 cleanup surfaces: `WorkflowExecutor` runtime dual-writes, `WorkflowRunSummary` / `WorkflowRunSummary::getMemos()` dual-read fallbacks, and the JSON columns on the `create_workflow_runs_table` migration. `tests/Unit/V2/VisibilityMetadataContractDocumentationTest.php` pins the contract: typed table named as authoritative storage, JSON column named as transitional artifact, no v2-alpha-to-v2 backwards compatibility, the cleanup surface list, and a regression check that fails closed if either document drifts back into multi-phase migration framing or alpha/beta backwards-compatibility language. This slice does not change runtime behavior. The runtime continues to dual-write and dual-read the JSON column for backwards compatibility with already-running v2-alpha clusters in development environments. The runtime-side cleanup is mechanical removal once issue #622 is scheduled.
1 parent 044ba07 commit 1091cf6

3 files changed

Lines changed: 256 additions & 35 deletions

File tree

docs/search-attributes-architecture.md

Lines changed: 31 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -307,23 +307,37 @@ class WorkflowSearchAttribute extends Model
307307

308308
All tests pass with proper database transactions and cleanup.
309309

310-
## Migration Strategy
311-
312-
### Forward Migration (v1 → v2)
313-
314-
1. **Phase 0**: Deploy table and model (this commit)
315-
2. **Phase 1**: Dual-write to both JSON blob and typed table
316-
3. **Phase 2**: Backfill existing JSON blobs to typed table
317-
4. **Phase 3**: Switch reads to typed table
318-
5. **Phase 4**: Remove JSON blob column (breaking change for v2.0)
319-
320-
### Backward Compatibility
321-
322-
For v2 alpha/beta releases:
323-
- Keep `workflow_runs.search_attributes` JSON column
324-
- Dual-write to both during transition
325-
- Waterline can read from either
326-
- Full cutover at v2.0 stable
310+
## v2 Visibility Metadata Contract
311+
312+
The `workflow_search_attributes` table is the authoritative storage for
313+
search attribute values in v2. Every read path that filters, sorts, or
314+
displays search attributes binds to this table or to a projection that
315+
derives from it. The `workflow_runs.search_attributes` JSON column is
316+
not part of the v2 contract: it is a transitional artifact left over
317+
from earlier v2-alpha development snapshots and will be removed before
318+
the v2.0 stable release.
319+
320+
There is no v2-alpha to v2 backwards-compatibility contract. v2 has
321+
never been released, so different v2 development snapshots are not a
322+
mixed fleet — they are iterations of an unreleased product. Operators
323+
upgrading from v1 follow the documented v1-to-v2 migration path; there
324+
is no separate v2-alpha cutover surface to preserve.
325+
326+
Required cleanup before v2.0 stable (tracked in issue #622):
327+
328+
- runtime stops dual-writing the JSON column from `WorkflowExecutor`
329+
- runtime stops dual-reading the JSON column as a fallback in
330+
`WorkflowRunSummary` and other projections
331+
- the `workflow_runs.search_attributes` column is dropped from the
332+
`create_workflow_runs_table` migration
333+
- this document and `docs/workflow-memos-architecture.md` describe the
334+
finalized typed-table contract with no transition phases or alpha
335+
fallbacks
336+
337+
Until that cleanup lands, the runtime continues to write the JSON
338+
column for backwards compatibility with already-running v2-alpha
339+
clusters in development environments. The runtime-side cleanup is a
340+
mechanical removal once issue #622 is scheduled.
327341

328342
## Waterline Integration
329343

docs/workflow-memos-architecture.md

Lines changed: 30 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -237,23 +237,36 @@ class WorkflowRunSummary extends Model
237237

238238
All tests pass with proper database transactions and cleanup.
239239

240-
## Migration Strategy
241-
242-
### Forward Migration (v1 → v2)
243-
244-
1. **Phase 0**: Deploy table and model (d061e5d)
245-
2. **Phase 1**: Dual-write to both JSON blob and typed table (4433d9e)
246-
3. **Phase 2**: Dual-read from typed table with JSON fallback (c50954b)
247-
4. **Phase 3**: Backfill existing JSON blobs to typed table (future)
248-
5. **Phase 4**: Remove JSON blob column (breaking change for v2.0)
249-
250-
### Backward Compatibility
251-
252-
For v2 alpha/beta releases:
253-
- Keep `workflow_runs.memo` JSON column
254-
- Dual-write to both during transition
255-
- Waterline can read from either via getMemos()
256-
- Full cutover at v2.0 stable
240+
## v2 Visibility Metadata Contract
241+
242+
The `workflow_memos` table is the authoritative storage for memo
243+
values in v2. Every detail/describe/list view that returns memos
244+
binds to this table or to a projection that derives from it. The
245+
`workflow_runs.memo` JSON column is not part of the v2 contract: it
246+
is a transitional artifact left over from earlier v2-alpha development
247+
snapshots and will be removed before the v2.0 stable release.
248+
249+
There is no v2-alpha to v2 backwards-compatibility contract. v2 has
250+
never been released, so different v2 development snapshots are not a
251+
mixed fleet — they are iterations of an unreleased product. Operators
252+
upgrading from v1 follow the documented v1-to-v2 migration path; there
253+
is no separate v2-alpha cutover surface to preserve.
254+
255+
Required cleanup before v2.0 stable (tracked in issue #622):
256+
257+
- runtime stops dual-writing the JSON column from `WorkflowExecutor`
258+
- runtime stops dual-reading the JSON column as a fallback in
259+
`WorkflowRunSummary::getMemos()` and other projections
260+
- the `workflow_runs.memo` column is dropped from the
261+
`create_workflow_runs_table` migration
262+
- this document and `docs/search-attributes-architecture.md` describe
263+
the finalized typed-table contract with no transition phases or
264+
alpha fallbacks
265+
266+
Until that cleanup lands, the runtime continues to write the JSON
267+
column for backwards compatibility with already-running v2-alpha
268+
clusters in development environments. The runtime-side cleanup is a
269+
mechanical removal once issue #622 is scheduled.
257270

258271
## Waterline Integration
259272

@@ -318,7 +331,6 @@ This separation ensures:
318331
✅ Memos support JSON-friendly values with explicit size/count limits
319332
✅ Continue-as-new inheritance is explicit and tracked
320333
✅ Memos are excluded from filtering by contract
321-
✅ Dual-write enables gradual migration
322334
✅ Projection layer exposes memos for detail views
323335
✅ Structural limit violations fail through typed exceptions
324336

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\Unit\V2;
6+
7+
use PHPUnit\Framework\TestCase;
8+
9+
/**
10+
* Pins the v2 visibility-metadata contract documented in
11+
* docs/search-attributes-architecture.md and docs/workflow-memos-architecture.md.
12+
*
13+
* The contract names typed tables (workflow_search_attributes,
14+
* workflow_memos) as the only authoritative storage for v2 search
15+
* attributes and memos. The legacy JSON columns on workflow_runs are
16+
* alpha-transition state and must be removed before v2.0 stable.
17+
*
18+
* v2 has never been released, so different v2 development snapshots
19+
* are not a "mixed fleet" and no v2-alpha-to-v2 backwards-compatibility
20+
* contract is owed. These tests fail closed if either document drifts
21+
* back into multi-phase migration framing or reintroduces alpha/beta
22+
* backwards-compatibility language.
23+
*/
24+
final class VisibilityMetadataContractDocumentationTest extends TestCase
25+
{
26+
private const SEARCH_ATTRIBUTES_DOCUMENT = 'docs/search-attributes-architecture.md';
27+
28+
private const MEMOS_DOCUMENT = 'docs/workflow-memos-architecture.md';
29+
30+
public function testSearchAttributesDocNamesTypedTableAsAuthoritativeStorage(): void
31+
{
32+
$contents = $this->documentContents(self::SEARCH_ATTRIBUTES_DOCUMENT);
33+
34+
$this->assertMatchesRegularExpression(
35+
'/`workflow_search_attributes`[^.]*authoritative storage/i',
36+
$contents,
37+
'Search-attributes architecture must name workflow_search_attributes as the authoritative v2 storage so projections, list filters, and Waterline visibility queries bind to one contract.',
38+
);
39+
}
40+
41+
public function testMemosDocNamesTypedTableAsAuthoritativeStorage(): void
42+
{
43+
$contents = $this->documentContents(self::MEMOS_DOCUMENT);
44+
45+
$this->assertMatchesRegularExpression(
46+
'/`workflow_memos`[^.]*authoritative storage/i',
47+
$contents,
48+
'Memos architecture must name workflow_memos as the authoritative v2 storage so detail/describe views bind to one contract.',
49+
);
50+
}
51+
52+
public function testSearchAttributesDocDeclaresJsonColumnAsTransitionalArtifact(): void
53+
{
54+
$contents = $this->documentContents(self::SEARCH_ATTRIBUTES_DOCUMENT);
55+
56+
$this->assertMatchesRegularExpression(
57+
'/`workflow_runs\.search_attributes`[\s\S]{0,400}transitional artifact[\s\S]{0,400}removed before[\s\S]{0,40}v2\.0 stable release/i',
58+
$contents,
59+
'Search-attributes architecture must declare workflow_runs.search_attributes JSON column as a transitional artifact slated for removal before v2.0 stable so the cutover is unambiguous.',
60+
);
61+
}
62+
63+
public function testMemosDocDeclaresJsonColumnAsTransitionalArtifact(): void
64+
{
65+
$contents = $this->documentContents(self::MEMOS_DOCUMENT);
66+
67+
$this->assertMatchesRegularExpression(
68+
'/`workflow_runs\.memo`[\s\S]{0,400}transitional artifact[\s\S]{0,400}removed before[\s\S]{0,40}v2\.0 stable release/i',
69+
$contents,
70+
'Memos architecture must declare workflow_runs.memo JSON column as a transitional artifact slated for removal before v2.0 stable so the cutover is unambiguous.',
71+
);
72+
}
73+
74+
public function testSearchAttributesDocRulesOutAlphaToV2BackwardsCompatibility(): void
75+
{
76+
$contents = $this->documentContents(self::SEARCH_ATTRIBUTES_DOCUMENT);
77+
78+
$this->assertMatchesRegularExpression(
79+
'/no v2-alpha to v2 backwards-compatibility contract/i',
80+
$contents,
81+
'Search-attributes architecture must state there is no v2-alpha-to-v2 backwards-compatibility contract so the clean-slate v2 release framing is explicit.',
82+
);
83+
}
84+
85+
public function testMemosDocRulesOutAlphaToV2BackwardsCompatibility(): void
86+
{
87+
$contents = $this->documentContents(self::MEMOS_DOCUMENT);
88+
89+
$this->assertMatchesRegularExpression(
90+
'/no v2-alpha to v2 backwards-compatibility contract/i',
91+
$contents,
92+
'Memos architecture must state there is no v2-alpha-to-v2 backwards-compatibility contract so the clean-slate v2 release framing is explicit.',
93+
);
94+
}
95+
96+
public function testSearchAttributesDocNamesRequiredCleanupSurfaces(): void
97+
{
98+
$contents = $this->documentContents(self::SEARCH_ATTRIBUTES_DOCUMENT);
99+
100+
foreach (['WorkflowExecutor', 'WorkflowRunSummary', 'create_workflow_runs_table'] as $surface) {
101+
$this->assertStringContainsString(
102+
$surface,
103+
$contents,
104+
sprintf(
105+
'Search-attributes architecture must name %s as part of the required pre-v2.0 cleanup so reviewers know which surfaces still carry the JSON-column path.',
106+
$surface
107+
),
108+
);
109+
}
110+
}
111+
112+
public function testMemosDocNamesRequiredCleanupSurfaces(): void
113+
{
114+
$contents = $this->documentContents(self::MEMOS_DOCUMENT);
115+
116+
foreach ([
117+
'WorkflowExecutor',
118+
'WorkflowRunSummary::getMemos()',
119+
'create_workflow_runs_table',
120+
] as $surface) {
121+
$this->assertStringContainsString(
122+
$surface,
123+
$contents,
124+
sprintf(
125+
'Memos architecture must name %s as part of the required pre-v2.0 cleanup so reviewers know which surfaces still carry the JSON-column path.',
126+
$surface
127+
),
128+
);
129+
}
130+
}
131+
132+
public function testSearchAttributesDocCitesIssue622AsCleanupTracker(): void
133+
{
134+
$contents = $this->documentContents(self::SEARCH_ATTRIBUTES_DOCUMENT);
135+
136+
$this->assertMatchesRegularExpression(
137+
'/issue #622/i',
138+
$contents,
139+
'Search-attributes architecture must cite the visibility-metadata cleanup tracker so future workers can find the open follow-up.',
140+
);
141+
}
142+
143+
public function testMemosDocCitesIssue622AsCleanupTracker(): void
144+
{
145+
$contents = $this->documentContents(self::MEMOS_DOCUMENT);
146+
147+
$this->assertMatchesRegularExpression(
148+
'/issue #622/i',
149+
$contents,
150+
'Memos architecture must cite the visibility-metadata cleanup tracker so future workers can find the open follow-up.',
151+
);
152+
}
153+
154+
public function testSearchAttributesDocRetiresMultiPhaseMigrationLadder(): void
155+
{
156+
$contents = $this->documentContents(self::SEARCH_ATTRIBUTES_DOCUMENT);
157+
158+
$this->assertStringNotContainsString(
159+
'Phase 0**: Deploy table and model',
160+
$contents,
161+
'Search-attributes architecture must not reintroduce the Phase 0/1/2/3/4 migration ladder; the v2 contract is one storage surface, not a multi-phase rollout.',
162+
);
163+
$this->assertStringNotContainsString(
164+
'For v2 alpha/beta releases:',
165+
$contents,
166+
'Search-attributes architecture must not reintroduce the v2 alpha/beta backwards-compatibility section; v2 is one product, not a sequence of supported alpha snapshots.',
167+
);
168+
}
169+
170+
public function testMemosDocRetiresMultiPhaseMigrationLadder(): void
171+
{
172+
$contents = $this->documentContents(self::MEMOS_DOCUMENT);
173+
174+
$this->assertStringNotContainsString(
175+
'Phase 0**: Deploy table and model',
176+
$contents,
177+
'Memos architecture must not reintroduce the Phase 0/1/2/3/4 migration ladder; the v2 contract is one storage surface, not a multi-phase rollout.',
178+
);
179+
$this->assertStringNotContainsString(
180+
'For v2 alpha/beta releases:',
181+
$contents,
182+
'Memos architecture must not reintroduce the v2 alpha/beta backwards-compatibility section; v2 is one product, not a sequence of supported alpha snapshots.',
183+
);
184+
}
185+
186+
private function documentContents(string $relativePath): string
187+
{
188+
$path = dirname(__DIR__, 3) . '/' . $relativePath;
189+
$contents = file_get_contents($path);
190+
191+
$this->assertIsString($contents, sprintf('Could not read %s.', $relativePath));
192+
193+
return $contents;
194+
}
195+
}

0 commit comments

Comments
 (0)