gowalk-cicd 1.0.151 → 1.0.152

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.
@@ -1 +1 @@
1
- 1.0.151
1
+ 1.0.152
@@ -1 +1 @@
1
- 1.0.151
1
+ 1.0.152
@@ -1 +1 @@
1
- 1.0.151
1
+ 1.0.152
@@ -10,7 +10,8 @@ FAILURES = frozenset({
10
10
  'pool_provenance_unknown', 'pool_inventory_invalid', 'host_routes_unsupported',
11
11
  'host_rules_unsupported', 'host_addresses_invalid', 'resolver_inventory_invalid',
12
12
  'resolver_upstream_unknown',
13
- })
13
+ }) | frozenset('host_rules_unsupported_' + part for part in
14
+ ('count', 'fields', 'priority', 'source', 'table', 'structure', 'fwmark', 'fwmask', 'action', 'duplicate'))
14
15
  PRIVATE = tuple(ipaddress.ip_network(value) for value in ('10.0.0.0/8', '172.16.0.0/12', '192.168.0.0/16'))
15
16
  DEVICE = re.compile(r'[A-Za-z0-9][A-Za-z0-9_.:-]{0,14}')
16
17
 
@@ -195,7 +196,7 @@ def validate_rules(rows):
195
196
  """Admit ordinary Linux rules or the exact Tailscale v1.88.2 baseIPRules chain."""
196
197
  # https://github.com/tailscale/tailscale/blob/v1.88.2/wgengine/router/router_linux.go
197
198
  if not isinstance(rows, list) or len(rows) not in (3, 7):
198
- raise InventoryFailure('host_rules_unsupported')
199
+ raise InventoryFailure('host_rules_unsupported_count')
199
200
  expected = [{'priority': p, 'src': 'all', 'table': t}
200
201
  for p, t in ((0, 'local'), (32766, 'main'), (32767, 'default'))]
201
202
  if len(rows) == 7:
@@ -205,21 +206,32 @@ def validate_rules(rows):
205
206
  actual = []
206
207
  tables = {255: 'local', 254: 'main', 253: 'default'}
207
208
  for row in rows:
208
- if (not isinstance(row, dict) or set(row) - {'priority', 'src', 'table', 'protocol',
209
- 'fwmark', 'fwmask', 'action'}
210
- or type(row.get('priority')) is not int or row.get('src') != 'all'):
211
- raise InventoryFailure('host_rules_unsupported')
209
+ if not isinstance(row, dict) or set(row) - {'priority', 'src', 'table', 'protocol',
210
+ 'fwmark', 'fwmask', 'action'}:
211
+ raise InventoryFailure('host_rules_unsupported_fields')
212
+ if type(row.get('priority')) is not int:
213
+ raise InventoryFailure('host_rules_unsupported_priority')
214
+ if row.get('src') != 'all':
215
+ raise InventoryFailure('host_rules_unsupported_source')
212
216
  normalized = {key: value for key, value in row.items() if key != 'protocol'}
213
217
  table = normalized.get('table')
214
218
  if table is not None:
215
219
  if type(table) not in (str, int):
216
- raise InventoryFailure('host_rules_unsupported')
220
+ raise InventoryFailure('host_rules_unsupported_table')
217
221
  normalized['table'] = tables.get(table, table)
218
- if normalized not in expected or normalized in actual:
219
- raise InventoryFailure('host_rules_unsupported')
222
+ wanted = next((item for item in expected if item['priority'] == normalized['priority']), None)
223
+ if wanted is None:
224
+ raise InventoryFailure('host_rules_unsupported_priority')
225
+ if set(normalized) != set(wanted):
226
+ raise InventoryFailure('host_rules_unsupported_structure')
227
+ for field in ('table', 'fwmark', 'fwmask', 'action'):
228
+ if normalized.get(field) != wanted.get(field):
229
+ raise InventoryFailure('host_rules_unsupported_' + field)
230
+ if normalized in actual:
231
+ raise InventoryFailure('host_rules_unsupported_duplicate')
220
232
  actual.append(normalized)
221
233
  if len(actual) != len(expected):
222
- raise InventoryFailure('host_rules_unsupported')
234
+ raise InventoryFailure('host_rules_unsupported_count')
223
235
 
224
236
 
225
237
  def resolver_exclusions(raw, *, resolved=False, records=None):
@@ -0,0 +1,53 @@
1
+ """Classify refused policy shapes without publishing their private values or relaxing admission."""
2
+ import copy
3
+ import json
4
+ import os
5
+ import unittest
6
+ from unittest.mock import patch
7
+
8
+ from network_diagnostics import SCHEMA, decode
9
+ from network_inventory import collect
10
+ from network_pools import InventoryFailure, validate_rules
11
+ from test_network_inventory import FakeHost, RULES
12
+
13
+
14
+ class RuleDiagnosticTests(unittest.TestCase):
15
+ def test_each_rejected_shape_keeps_a_closed_reason_without_values(self):
16
+ marked = {'src': 'all', 'fwmark': '0x80000', 'fwmask': '0xff0000'}
17
+ valid = RULES + [dict(marked, priority=5210, table='main'), dict(marked, priority=5230, table='default'),
18
+ dict(marked, priority=5250, action='unreachable'),
19
+ {'priority': 5270, 'src': 'all', 'table': 52}]
20
+ validate_rules(valid)
21
+ cases = [('fields', 'PRIVATE_SENTINEL', 'PRIVATE_SENTINEL'), ('priority', 'priority', True),
22
+ ('source', 'src', 'PRIVATE_SENTINEL'), ('table', 'table', 'PRIVATE_SENTINEL'),
23
+ ('fwmark', 'fwmark', 'PRIVATE_SENTINEL'), ('fwmask', 'fwmask', 'PRIVATE_SENTINEL')]
24
+ for reason, key, value in cases:
25
+ rows = copy.deepcopy(valid)
26
+ rows[3][key] = value
27
+ with self.subTest(reason=reason), self.assertRaises(InventoryFailure) as caught:
28
+ validate_rules(rows)
29
+ self.assertEqual(caught.exception.code, 'host_rules_unsupported_' + reason)
30
+ self.assertNotIn('PRIVATE_SENTINEL', str(caught.exception))
31
+ for rows, reason in ((valid[:-1], 'count'), (valid[:-1] + [valid[0]], 'duplicate'),
32
+ ([dict(row, fwmark='0x80000') for row in RULES], 'structure')):
33
+ with self.subTest(reason=reason), self.assertRaises(InventoryFailure) as caught:
34
+ validate_rules(rows)
35
+ self.assertEqual(caught.exception.code, 'host_rules_unsupported_' + reason)
36
+
37
+ def test_scoped_diagnostic_retains_reason_and_partial_metadata_only(self):
38
+ host = FakeHost()
39
+ host.answers[('ip', '-j', '-4', 'rule', 'show')][0]['src'] = 'PRIVATE_SENTINEL'
40
+ with patch.dict(os.environ, {}, clear=True), self.assertRaises(InventoryFailure) as caught:
41
+ collect(host, require_pools=False)
42
+ failure = caught.exception
43
+ result = dict(failure.details, schema=SCHEMA, ok=False, project='fixture', failure=failure.code)
44
+ raw = json.dumps(result).encode()
45
+ self.assertEqual(decode(1, raw, 'fixture')['failure'], 'host_rules_unsupported_source')
46
+ self.assertFalse(result['complete'])
47
+ self.assertNotIn(b'PRIVATE_SENTINEL', raw)
48
+ self.assertNotIn('networks', result)
49
+ self.assertFalse(any('create' in call or 'rm' in call for call in host.calls))
50
+
51
+
52
+ if __name__ == '__main__':
53
+ unittest.main()
@@ -79,6 +79,9 @@ A routing refusal can retain safe engine/pool metadata with `complete:false` and
79
79
  partial evidence never admits allocation. A successful diagnostic is a readback, not a deployment,
80
80
  capacity reservation, proof of application health or proof that another project cannot reach the app.
81
81
  Raw container data, labels, environment, daemon configuration and command errors are not exported.
82
+ Policy-rule refusals use `host_rules_unsupported_<reason>` to distinguish count, fields, priority,
83
+ source, table, structure, fwmark, fwmask, action and duplicate mismatches. These fixed classifications
84
+ retain the strict supported-rule comparison and publish no rejected keys or values.
82
85
 
83
86
  The deployment emits its own `gowalk-cicd/backend-networks.v1` receipt. The existing
84
87
  `backend_deploy_failed` phase remains `compose_up` on a preparation refusal. Normal public health,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gowalk-cicd",
3
- "version": "1.0.151",
3
+ "version": "1.0.152",
4
4
  "description": "Zero-config GitHub Actions delivery for iOS TestFlight and Android Google Play.",
5
5
  "type": "module",
6
6
  "bin": {