Skip to content

Commit 03c5d22

Browse files
Conformance finding: Waterline namespace operator visibility remains unproved (#174)
1 parent 71d1c76 commit 03c5d22

3 files changed

Lines changed: 170 additions & 1 deletion

File tree

CONFORMANCE.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,10 @@ routes, emits a
9393
`durable-workflow.v2.namespace-runtime.result` document with
9494
`waterline_operator_namespace_visibility` populated, includes the scoped and
9595
unscoped API and dashboard response captures used by the pass/fail checks,
96-
and removes its fixture rows unless `--keep-fixtures` is supplied. It is a shard, not a full
96+
adds an `operator_surface_matrix` verdict for scoped workflow lists,
97+
workflow details, schedule views, search-attribute values, dashboard scope,
98+
stats/operator APIs, and documented unscoped authority, and removes its
99+
fixture rows unless `--keep-fixtures` is supplied. It is a shard, not a full
97100
namespace run: all non-Waterline namespace scenarios remain `not_covered` for
98101
the full harness to merge or evaluate separately.
99102

app/Console/NamespaceConformanceCommand.php

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,11 @@ public function handle(HttpKernel $kernel): int
161161
],
162162
'fixture_ids' => $fixtures,
163163
];
164+
$evidence['operator_surface_matrix'] = $this->operatorSurfaceMatrix(
165+
$tenantAEvidence,
166+
$tenantBEvidence,
167+
$unscopedAuthority,
168+
);
164169

165170
$scenarioPassed = $this->waterlineEvidencePassed($evidence);
166171
$waterlineScenario = [
@@ -1021,6 +1026,140 @@ private function unscopedDashboardViewPass(array $unscoped): bool
10211026
&& data_get($unscoped, 'api_captures.dashboard_view.path') === '/';
10221027
}
10231028

1029+
/**
1030+
* @param array<string, mixed> $tenantA
1031+
* @param array<string, mixed> $tenantB
1032+
* @param array<string, mixed> $unscoped
1033+
* @return array<string, mixed>
1034+
*/
1035+
private function operatorSurfaceMatrix(array $tenantA, array $tenantB, array $unscoped): array
1036+
{
1037+
return [
1038+
'tenant_scoped_surfaces' => [
1039+
'tenant_a' => $this->tenantOperatorSurfaceVerdict($tenantA),
1040+
'tenant_b' => $this->tenantOperatorSurfaceVerdict($tenantB),
1041+
],
1042+
'unscoped_authority' => $this->unscopedOperatorSurfaceVerdict($unscoped),
1043+
];
1044+
}
1045+
1046+
/**
1047+
* @param array<string, mixed> $tenant
1048+
* @return array<string, mixed>
1049+
*/
1050+
private function tenantOperatorSurfaceVerdict(array $tenant): array
1051+
{
1052+
$namespace = $tenant['namespace'] ?? null;
1053+
$namespace = is_string($namespace) ? $namespace : '';
1054+
1055+
return [
1056+
'namespace' => $namespace,
1057+
'active_namespace_visible' => $namespace !== '' && $this->tenantDashboardViewPass($tenant, $namespace),
1058+
'workflow_list_scoped' => data_get($tenant, 'workflow_list.status') === 200
1059+
&& data_get($tenant, 'workflow_list.operator_scope.mode') === 'namespace'
1060+
&& data_get($tenant, 'workflow_list.operator_scope.namespace') === $namespace
1061+
&& data_get($tenant, 'workflow_list.includes_own_run') === true
1062+
&& data_get($tenant, 'workflow_list.excludes_foreign_run') === true,
1063+
'workflow_detail_scoped' => data_get($tenant, 'workflow_detail.status') === 200
1064+
&& data_get($tenant, 'workflow_detail.operator_scope.mode') === 'namespace'
1065+
&& data_get($tenant, 'workflow_detail.operator_scope.namespace') === $namespace
1066+
&& data_get($tenant, 'workflow_detail.namespace') === $namespace
1067+
&& data_get($tenant, 'foreign_workflow_detail.not_found') === true,
1068+
'schedule_list_scoped' => data_get($tenant, 'schedule_list.status') === 200
1069+
&& data_get($tenant, 'schedule_list.operator_scope.mode') === 'namespace'
1070+
&& data_get($tenant, 'schedule_list.operator_scope.namespace') === $namespace
1071+
&& data_get($tenant, 'schedule_list.includes_own_schedule') === true
1072+
&& data_get($tenant, 'schedule_list.excludes_foreign_schedule') === true,
1073+
'schedule_detail_scoped' => data_get($tenant, 'schedule_detail.status') === 200
1074+
&& data_get($tenant, 'schedule_detail.operator_scope.mode') === 'namespace'
1075+
&& data_get($tenant, 'schedule_detail.operator_scope.namespace') === $namespace
1076+
&& data_get($tenant, 'schedule_detail.namespace') === $namespace
1077+
&& data_get($tenant, 'foreign_schedule_detail.not_found') === true,
1078+
'search_attribute_values_scoped' => $this->captureSearchAttributePass($tenant, 'workflow_list')
1079+
&& $this->captureSearchAttributePass($tenant, 'workflow_detail')
1080+
&& $this->captureSearchAttributePass($tenant, 'schedule_list')
1081+
&& $this->captureSearchAttributePass($tenant, 'schedule_detail'),
1082+
'operator_api_scoped' => data_get($tenant, 'operator_api_stats.status') === 200
1083+
&& data_get($tenant, 'operator_api_stats.operator_scope.mode') === 'namespace'
1084+
&& data_get($tenant, 'operator_api_stats.operator_scope.namespace') === $namespace
1085+
&& data_get($tenant, 'operator_api_stats.excludes_foreign_run') === true,
1086+
'api_captures_scoped' => $namespace !== '' && $this->tenantApiCapturesPass($tenant, $namespace),
1087+
];
1088+
}
1089+
1090+
/**
1091+
* @param array<string, mixed> $unscoped
1092+
* @return array<string, mixed>
1093+
*/
1094+
private function unscopedOperatorSurfaceVerdict(array $unscoped): array
1095+
{
1096+
return [
1097+
'documented_cluster_authority' => data_get($unscoped, 'documented_safe_authority') === true,
1098+
'dashboard_cluster_authority_visible' => $this->unscopedDashboardViewPass($unscoped),
1099+
'workflow_list_cluster_authority' => data_get($unscoped, 'workflow_list.status') === 200
1100+
&& data_get($unscoped, 'workflow_list.operator_scope.mode') === 'cluster'
1101+
&& data_get($unscoped, 'workflow_list.operator_scope.namespace') === null
1102+
&& data_get($unscoped, 'workflow_list.includes_tenant_a_run') === true
1103+
&& data_get($unscoped, 'workflow_list.includes_tenant_b_run') === true,
1104+
'schedule_list_cluster_authority' => data_get($unscoped, 'schedule_list.status') === 200
1105+
&& data_get($unscoped, 'schedule_list.operator_scope.mode') === 'cluster'
1106+
&& data_get($unscoped, 'schedule_list.operator_scope.namespace') === null
1107+
&& data_get($unscoped, 'schedule_list.includes_tenant_a_schedule') === true
1108+
&& data_get($unscoped, 'schedule_list.includes_tenant_b_schedule') === true,
1109+
'operator_api_cluster_authority' => data_get($unscoped, 'operator_api_stats.status') === 200
1110+
&& data_get($unscoped, 'operator_api_stats.operator_scope.mode') === 'cluster'
1111+
&& data_get($unscoped, 'operator_api_stats.operator_scope.namespace') === null
1112+
&& data_get($unscoped, 'operator_api_stats.flow_count_covers_fixture_runs') === true,
1113+
];
1114+
}
1115+
1116+
/**
1117+
* @param array<string, mixed> $matrix
1118+
*/
1119+
private function operatorSurfaceMatrixPass(array $matrix): bool
1120+
{
1121+
foreach (['tenant_a', 'tenant_b'] as $tenantKey) {
1122+
$tenant = data_get($matrix, 'tenant_scoped_surfaces.'.$tenantKey);
1123+
if (! is_array($tenant) || ! is_string($tenant['namespace'] ?? null) || $tenant['namespace'] === '') {
1124+
return false;
1125+
}
1126+
1127+
foreach ([
1128+
'active_namespace_visible',
1129+
'workflow_list_scoped',
1130+
'workflow_detail_scoped',
1131+
'schedule_list_scoped',
1132+
'schedule_detail_scoped',
1133+
'search_attribute_values_scoped',
1134+
'operator_api_scoped',
1135+
'api_captures_scoped',
1136+
] as $field) {
1137+
if (($tenant[$field] ?? null) !== true) {
1138+
return false;
1139+
}
1140+
}
1141+
}
1142+
1143+
$unscoped = data_get($matrix, 'unscoped_authority');
1144+
if (! is_array($unscoped)) {
1145+
return false;
1146+
}
1147+
1148+
foreach ([
1149+
'documented_cluster_authority',
1150+
'dashboard_cluster_authority_visible',
1151+
'workflow_list_cluster_authority',
1152+
'schedule_list_cluster_authority',
1153+
'operator_api_cluster_authority',
1154+
] as $field) {
1155+
if (($unscoped[$field] ?? null) !== true) {
1156+
return false;
1157+
}
1158+
}
1159+
1160+
return true;
1161+
}
1162+
10241163
private function captureStatusAndScopePass(
10251164
mixed $capture,
10261165
string $path,
@@ -1080,6 +1219,11 @@ private function captureSearchAttributePass(array $tenant, string $evidenceKey):
10801219
*/
10811220
private function waterlineEvidencePassed(array $evidence): bool
10821221
{
1222+
$matrix = $evidence['operator_surface_matrix'] ?? [];
1223+
if (! is_array($matrix) || ! $this->operatorSurfaceMatrixPass($matrix)) {
1224+
return false;
1225+
}
1226+
10831227
foreach (['tenant_a_scoped_views', 'tenant_b_scoped_views'] as $tenantKey) {
10841228
$tenant = $evidence[$tenantKey] ?? [];
10851229
if (! is_array($tenant)) {

tests/Feature/NamespaceConformanceCommandTest.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,20 @@ public function testCommandEmitsWaterlineOperatorNamespaceVisibilityShard(): voi
123123
$this->assertTrue($visibility['unscoped_view_authority']['schedule_list']['includes_tenant_b_schedule']);
124124
$this->assertSame('billing-schedule', $visibility['unscoped_view_authority']['schedule_list']['tenant_a_search_attribute_visible']);
125125
$this->assertSame('shipping-schedule', $visibility['unscoped_view_authority']['schedule_list']['tenant_b_search_attribute_visible']);
126+
$this->assertSame('billing', $visibility['operator_surface_matrix']['tenant_scoped_surfaces']['tenant_a']['namespace']);
127+
$this->assertTrue($visibility['operator_surface_matrix']['tenant_scoped_surfaces']['tenant_a']['active_namespace_visible']);
128+
$this->assertTrue($visibility['operator_surface_matrix']['tenant_scoped_surfaces']['tenant_a']['workflow_list_scoped']);
129+
$this->assertTrue($visibility['operator_surface_matrix']['tenant_scoped_surfaces']['tenant_a']['workflow_detail_scoped']);
130+
$this->assertTrue($visibility['operator_surface_matrix']['tenant_scoped_surfaces']['tenant_a']['schedule_list_scoped']);
131+
$this->assertTrue($visibility['operator_surface_matrix']['tenant_scoped_surfaces']['tenant_a']['schedule_detail_scoped']);
132+
$this->assertTrue($visibility['operator_surface_matrix']['tenant_scoped_surfaces']['tenant_a']['search_attribute_values_scoped']);
133+
$this->assertTrue($visibility['operator_surface_matrix']['tenant_scoped_surfaces']['tenant_a']['operator_api_scoped']);
134+
$this->assertTrue($visibility['operator_surface_matrix']['tenant_scoped_surfaces']['tenant_b']['workflow_list_scoped']);
135+
$this->assertTrue($visibility['operator_surface_matrix']['unscoped_authority']['documented_cluster_authority']);
136+
$this->assertTrue($visibility['operator_surface_matrix']['unscoped_authority']['dashboard_cluster_authority_visible']);
137+
$this->assertTrue($visibility['operator_surface_matrix']['unscoped_authority']['workflow_list_cluster_authority']);
138+
$this->assertTrue($visibility['operator_surface_matrix']['unscoped_authority']['schedule_list_cluster_authority']);
139+
$this->assertTrue($visibility['operator_surface_matrix']['unscoped_authority']['operator_api_cluster_authority']);
126140
$this->assertWaterlineEvidencePassCriteriaRequireHttpCaptures($visibility);
127141

128142
$this->assertSame('not_covered', $scenarios['nexus_explicit_cross_namespace_invocation']['status']);
@@ -182,6 +196,14 @@ function (array $evidence): bool {
182196
data_set($wrongStatsScope, 'tenant_a_scoped_views.operator_api_stats.operator_scope.namespace', 'shipping');
183197
$this->assertFalse($passes($wrongStatsScope));
184198

199+
$missingVerdictMatrix = $visibility;
200+
unset($missingVerdictMatrix['operator_surface_matrix']);
201+
$this->assertFalse($passes($missingVerdictMatrix));
202+
203+
$failedVerdictMatrix = $visibility;
204+
data_set($failedVerdictMatrix, 'operator_surface_matrix.tenant_scoped_surfaces.tenant_a.workflow_list_scoped', false);
205+
$this->assertFalse($passes($failedVerdictMatrix));
206+
185207
$missingCapturedOwnSearchAttribute = $visibility;
186208
data_set($missingCapturedOwnSearchAttribute, 'tenant_a_scoped_views.api_captures.workflow_list.json.data.0.search_attributes.tenant_marker', null);
187209
$this->assertFalse($passes($missingCapturedOwnSearchAttribute));

0 commit comments

Comments
 (0)