Integrate reviewed ordinary notification runtime CI guard

Preserve exact Delivery commits and retained real-embed proof; check ordinary saved-policy delivery and restart without product or permission changes.

Change-source: pulse-maintainer
This commit is contained in:
pulse-triage[bot]
2026-10-11 05:34:44 +01:00
3 changed files with 595 additions and 0 deletions
+20
View File
@@ -319,6 +319,26 @@ jobs:
if-no-files-found: warn
retention-days: 7
# Reuse the exact real-embed binary, rather than another build or mock
# lifecycle callback. This owns only disposable loopback endpoints/data:
# saved policy must reach ordinary alerts and survive a real restart.
- name: Check ordinary notification policy and restart
timeout-minutes: 3
run: >-
python3 scripts/check_notification_runtime.py
--binary "$RUNNER_TEMP/pulse-real-embed"
--expected-source-sha "${{ github.sha }}"
--receipt "$RUNNER_TEMP/pulse-notification-runtime.json"
- name: Retain bounded notification runtime receipt
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: notification-runtime-${{ github.run_id }}-${{ github.run_attempt }}
path: ${{ runner.temp }}/pulse-notification-runtime.json
if-no-files-found: warn
retention-days: 7
# An audit service failure or real advisory stays gating, but it must not
# suppress formatting, tests, type-checking, or production-build proof.
- name: Require frontend dependency audit
+381
View File
@@ -0,0 +1,381 @@
#!/usr/bin/env python3
"""An owned offline source application and webhook; not installed acceptance.
All data, credentials and network endpoints are generated in this guest.
Never prints credentials, response bodies or application logs. Mutations are
single submissions; uncertain POST/PUT outcomes stop without replay.
"""
import argparse
import datetime
import hashlib
import http.cookiejar
import http.server
import json
import os
from pathlib import Path
import re
import secrets
import signal
import socket
import subprocess
import tempfile
import threading
import time
import urllib.error
import urllib.request
PUBLIC_URL = 'https://notification-runtime.example.test'
LIMIT = 2 * 1024 * 1024
# prepareWebhookData's ordinary event; resolution is a different path.
ORDINARY_EVENT = 'alert'
class Failure(RuntimeError):
pass
def require(condition, message):
if not condition:
raise Failure(message)
def free_port():
with socket.socket() as sock:
sock.bind(('127.0.0.1', 0))
return sock.getsockname()[1]
class Sink:
def __init__(self):
self.rows = []
self.lock = threading.Lock()
self.error = None
owner = self
class Handler(http.server.BaseHTTPRequestHandler):
def log_message(self, *_):
pass
def do_POST(self):
try:
size = int(self.headers.get('Content-Length', '-1'))
require(0 < size <= 65536, 'sink request exceeded declared bound')
raw = self.rfile.read(size)
require(len(raw) == size, 'sink short read')
obj = json.loads(raw)
require(isinstance(obj, dict), 'sink non-object')
with owner.lock:
owner.rows.append({'body': obj, 'sha256': hashlib.sha256(raw).hexdigest(),
'bytes': size, 'path': self.path})
self.send_response(200)
self.send_header('Content-Length', '2')
self.end_headers()
self.wfile.write(b'OK')
except Exception as exc:
owner.error = type(exc).__name__
self.send_response(400)
self.send_header('Content-Length', '0')
self.end_headers()
self.server = http.server.ThreadingHTTPServer(('127.0.0.1', 0), Handler)
self.server.daemon_threads = False
self.port = self.server.server_port
self.thread = threading.Thread(target=self.server.serve_forever, name='owned-webhook')
self.thread.start()
def snapshot(self):
with self.lock:
return list(self.rows)
def close(self):
self.server.shutdown()
self.server.server_close()
self.thread.join(timeout=5)
require(not self.thread.is_alive(), 'sink thread did not join')
with socket.socket() as sock:
sock.settimeout(1)
require(sock.connect_ex(('127.0.0.1', self.port)) != 0, 'sink listener stayed open')
def delivery_checks(row, expected_name):
"""Keep each asserted closed condition, not private/untrusted payload text."""
body = row['body']
return {'owned_path': row['path'] == '/owned-alert',
'resource_identity': body.get('resourceName') == expected_name,
'saved_public_url': body.get('instance') == PUBLIC_URL,
'ordinary_event': body.get('event') == ORDINARY_EVENT,
'cpu_metric': body.get('type') == 'cpu'}
def source_binding(binary, expected):
require(bool(re.fullmatch(r'[0-9a-f]{40}', expected)), 'invalid expected source identity')
actual = subprocess.check_output(['git', 'rev-parse', 'HEAD'], text=True).strip()
require(actual == expected, 'checkout differs from expected source')
require(subprocess.run(['git', 'diff', '--quiet', 'HEAD'], check=False).returncode == 0,
'checkout has tracked source changes')
version = subprocess.check_output(['go', 'version', '-m', str(binary)], text=True)
fields = {}
for line in version.splitlines():
match = re.match(r'\s*build\s+(vcs(?:\.[a-z]+)?)=(.*)$', line)
if match:
fields[match[1]] = match[2]
require(fields.get('vcs') == 'git' and fields.get('vcs.revision') == expected and
fields.get('vcs.modified') == 'false', 'binary VCS identity is not clean exact source')
return {'checkout_sha': actual, 'binary_vcs_revision': fields['vcs.revision'],
'binary_vcs_modified': False}
class Journey:
def __init__(self, binary, data, log, sink):
self.binary, self.data, self.log, self.sink = binary, data, log, sink
self.port = free_port()
self.origin = f'http://127.0.0.1:{self.port}'
self.cookies = http.cookiejar.CookieJar()
self.client = urllib.request.build_opener(urllib.request.ProxyHandler({}),
urllib.request.HTTPCookieProcessor(self.cookies))
self.password = secrets.token_hex(24)
self.token = None
self.token_id = None
self.process = None
self.phases, self.exits, self.requests, self.deliveries = [], [], [], []
self.delivery_observations = []
self.active_request = None
def phase(self, name):
self.phases.append(name)
print('PHASE=' + name, flush=True)
def request(self, path, payload=None, method=None, expected=200, auth='session'):
headers = {'Content-Type': 'application/json', 'User-Agent': 'pulse-notification-policy-journey'}
client = self.client
if auth != 'session':
client = urllib.request.build_opener(urllib.request.ProxyHandler({}))
if auth == 'agent':
headers['Authorization'] = 'Bearer ' + self.token
if payload is not None and auth == 'session':
for cookie in self.cookies:
if cookie.name == 'pulse_csrf':
headers['X-CSRF-Token'] = cookie.value
req = urllib.request.Request(self.origin + path, headers=headers, method=method,
data=None if payload is None else json.dumps(payload).encode())
self.active_request = {'path': path, 'method': req.get_method(), 'authentication': auth}
print('REQUEST_BEGIN=' + json.dumps(self.active_request, sort_keys=True), flush=True)
start = time.monotonic()
try:
response = client.open(req, timeout=30)
except urllib.error.HTTPError as exc:
response = exc
with response:
raw = response.read(LIMIT + 1)
obs = dict(self.active_request, status=response.status,
elapsed_seconds=round(time.monotonic() - start, 3))
self.requests.append(obs)
self.active_request = None
print('REQUEST_END=' + json.dumps(obs, sort_keys=True), flush=True)
require(len(raw) <= LIMIT, 'HTTP response exceeded byte bound')
require(response.status == expected, f'{path} expected HTTP {expected}, observed {response.status}')
if expected >= 400:
return None
try:
return json.loads(raw) if raw else None
except json.JSONDecodeError:
raise Failure(path + ' successful response was not JSON') from None
def start(self):
env = {'PATH': os.environ['PATH'], 'HOME': str(self.data), 'LANG': 'C.UTF-8',
'PULSE_DATA_DIR': str(self.data), 'BIND_ADDRESS': '127.0.0.1',
'FRONTEND_PORT': str(self.port), 'PULSE_METRICS_PORT': str(free_port()),
'PULSE_AUTH_USER': 'runtime-check', 'PULSE_AUTH_PASS': self.password,
'PULSE_MOCK_MODE': 'false', 'DISCOVERY_ENABLED': 'false', 'PULSE_TELEMETRY': 'false'}
self.process = subprocess.Popen([str(self.binary)], cwd=self.data, env=env,
stdout=self.log, stderr=subprocess.STDOUT, start_new_session=True)
deadline = time.monotonic() + 45
while time.monotonic() < deadline:
require(self.process.poll() is None, 'owned server exited before readiness')
try:
self.request('/api/resources', expected=401, auth='none')
return
except (urllib.error.URLError, TimeoutError, ConnectionError):
time.sleep(.2)
raise Failure('owned server readiness deadline expired')
def stop(self):
if self.process is None:
return
child, self.process = self.process, None
started = time.monotonic()
if child.poll() is None:
os.killpg(child.pid, signal.SIGTERM)
forced = False
try:
code = child.wait(timeout=40)
except subprocess.TimeoutExpired:
forced = True
os.killpg(child.pid, signal.SIGKILL)
code = child.wait(timeout=10)
self.exits.append({'exit': code, 'forced': forced, 'waited': True,
'wait_seconds': round(time.monotonic() - started, 3)})
require(code == 0 and not forced, 'owned server shutdown not clean')
with socket.socket() as sock:
sock.settimeout(1)
require(sock.connect_ex(('127.0.0.1', self.port)) != 0, 'owned listener stayed open')
def denied_destination(self):
self.request('/api/notifications/webhooks',
{'name': 'unlisted-loopback', 'url': f'http://127.0.0.2:{self.sink.port}/blocked',
'enabled': True, 'method': 'POST', 'service': 'generic'}, expected=400)
def save_system_policy(self):
# This process's deployment owns telemetry=false. Do not weaken that
# lock or include its field in an otherwise unrelated settings patch.
self.request('/api/system/settings/update', {'telemetryEnabled': False}, expected=409)
self.phase('deployment-telemetry-lock-preserved')
self.request('/api/system/settings/update', {'publicURL': PUBLIC_URL,
'webhookAllowedPrivateCIDRs': '127.0.0.1/32'})
def assert_settings(self):
settings = self.request('/api/system/settings')
require(settings.get('publicURL') == PUBLIC_URL and
settings.get('webhookAllowedPrivateCIDRs') == '127.0.0.1/32', 'saved system policy changed')
require(settings.get('telemetryEnabled') is False and
settings.get('envOverrides', {}).get('PULSE_TELEMETRY') is True,
'deployment telemetry restriction changed')
cfg = self.request('/api/alerts/config')
require(cfg.get('enabled') and cfg.get('activationState') == 'active' and
cfg.get('agentDefaults', {}).get('cpu', {}).get('trigger') == 80,
'saved threshold/activation policy changed')
def report(self, identity):
payload = {'agent': {'id': 'agent-' + identity, 'version': '0.0.0-dev', 'type': 'unified',
'commandsEnabled': False, 'intervalSeconds': 60},
'host': {'id': 'machine-' + identity, 'machineId': 'machine-' + identity,
'hostname': 'notification-' + identity + '.example.test',
'platform': 'linux', 'cpuCount': 1},
'timestamp': datetime.datetime.now(datetime.timezone.utc).isoformat(),
'sequenceId': 'v1:notification-' + identity + ':1',
'metrics': {'cpuUsagePercent': 95, 'memory': {'totalBytes': 1073741824,
'usedBytes': 429496729, 'freeBytes': 644245095, 'usage': 40}}}
self.request('/api/agents/agent/report', payload, auth='agent')
deadline = time.monotonic() + 30
expected_name = payload['host']['hostname']
while time.monotonic() < deadline:
require(self.process.poll() is None, 'owned server exited during ordinary delivery')
rows = self.sink.snapshot()
matches = [row for row in rows if row['body'].get('resourceName') == expected_name]
if matches:
require(len(matches) == 1, 'same first alert delivered more than once')
row = matches[0]
checks = delivery_checks(row, expected_name)
self.delivery_observations.append({'resource': identity, 'checks': checks,
'bytes': row['bytes'], 'sha256': row['sha256']})
require(all(checks.values()), 'ordinary delivery checks failed: ' +
','.join(sorted(key for key, passed in checks.items() if not passed)))
self.deliveries.append({'resource': identity, 'event': ORDINARY_EVENT, 'metric': 'cpu',
'saved_public_url': True, 'count': len(matches),
'bytes': row['bytes'], 'sha256': row['sha256']})
self.phase('ordinary-first-alert-' + identity)
return
require(self.sink.error is None, 'owned destination rejected payload')
time.sleep(.2)
active = self.request('/api/alerts/active')
self.phase('ordinary-delivery-deadline-active-' + ('present' if bool(active) else 'empty'))
raise Failure('ordinary first-alert destination deadline expired')
def run(self):
try:
self.start()
self.request('/api/login', {'username': 'runtime-check', 'password': self.password})
require(any(c.name == 'pulse_csrf' for c in self.cookies), 'login missing CSRF cookie')
require(self.request('/api/resources?limit=500').get('data') == [], 'initial live estate not empty')
self.phase('authenticated-empty-live-startup')
self.save_system_policy()
self.denied_destination()
self.request('/api/notifications/webhooks', {'name': 'owned-first-alert',
'url': f'http://127.0.0.1:{self.sink.port}/owned-alert', 'enabled': True,
'method': 'POST', 'service': 'generic',
'template': '{"instance":"{{.Instance | jsonString}}","resourceName":"{{.ResourceName | jsonString}}","event":"{{.Event | jsonString}}","type":"{{.Type | jsonString}}"}'})
cfg = self.request('/api/alerts/config')
cfg.update(enabled=True, activationState='active', flappingEnabled=False,
timeThresholds={'agent': 0}, metricTimeThresholds={'agent': {'cpu': 0}},
metricEvaluationWindows={'agent': {'cpu': 0}},
agentDefaults={'cpu': {'trigger': 80, 'clear': 70}},
disableAllAgents=False, disableAllAgentsOffline=True)
cfg['schedule'].update(cooldown=0, initialNotify='webhook', notifyOnResolve=False,
grouping={'enabled': False, 'window': 0, 'byNode': False, 'byGuest': False})
self.request('/api/alerts/config', cfg, method='PUT')
response = self.request('/api/security/tokens', {'name': 'owned-monitoring-only',
'scopes': ['agent:report'], 'expiresIn': '1h'})
self.token, self.token_id = response['token'], response['record']['id']
require(response['record']['scopes'] == ['agent:report'], 'monitoring-only scope widened')
self.request('/api/system/settings', expected=403, auth='agent')
self.assert_settings()
require(self.sink.snapshot() == [], 'destination called before ordinary synthetic alert')
self.phase('saved-policy-and-unlisted-destination-refusal')
self.report('a')
self.stop()
self.phase('first-child-exit-and-listener-closure')
self.start()
# Original authenticated cookies and issued token: no relogin or token replacement.
self.assert_settings()
tokens = self.request('/api/security/tokens')
records = tokens.get('tokens', []) if isinstance(tokens, dict) else tokens
matches = [r for r in records if r.get('id') == self.token_id]
require(len(matches) == 1 and matches[0].get('scopes') == ['agent:report'],
'issued monitoring-only token not retained at restart')
self.denied_destination()
self.phase('restart-session-token-and-policy-readback')
self.report('b')
self.stop()
self.phase('second-child-exit-and-listener-closure')
finally:
self.stop()
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('--binary', type=Path, required=True)
parser.add_argument('--expected-source-sha', required=True)
parser.add_argument('--receipt', type=Path)
args = parser.parse_args()
binary = args.binary.resolve(strict=True)
binding = source_binding(binary, args.expected_source_sha)
report = {'source_binding': binding, 'kind': 'offline-development-source-notification-runtime',
'binary_sha256': hashlib.sha256(binary.read_bytes()).hexdigest(), 'outcome': 'failed'}
with tempfile.TemporaryDirectory(prefix='pulse-notification-runtime-') as temporary:
data = Path(temporary)
sink = Sink()
journey = None
try:
with (data / 'owned-server.log').open('wb') as log:
journey = Journey(binary, data, log, sink)
journey.run()
report['outcome'] = 'passed'
except Exception as exc:
report['error'] = str(exc) if isinstance(exc, Failure) else type(exc).__name__
finally:
try:
sink.close()
report['sink_thread_joined_and_listener_closed'] = True
except Exception as exc:
report['sink_thread_joined_and_listener_closed'] = False
report['cleanup_error'] = type(exc).__name__
report['outcome'] = 'failed'
if journey:
report.update(phases=journey.phases, child_exits=journey.exits,
requests=journey.requests, incomplete_request=journey.active_request,
deliveries=journey.deliveries, delivery_observations=journey.delivery_observations)
report['owned_temporary_data_removed'] = not data.exists()
report['source_clean_after'] = subprocess.run(['git', 'diff', '--quiet', 'HEAD'], check=False).returncode == 0
if not report['source_clean_after']:
report['outcome'] = 'failed'
if args.receipt:
# Never replace a previous attempt's receipt. No logs or credentials.
with args.receipt.open('x', encoding='utf-8') as handle:
json.dump(report, handle, sort_keys=True, indent=2)
handle.write('\n')
print('NOTIFICATION_RUNTIME=' + json.dumps(report, sort_keys=True), flush=True)
return 0 if report['outcome'] == 'passed' else 1
if __name__ == '__main__':
raise SystemExit(main())
+194
View File
@@ -0,0 +1,194 @@
#!/usr/bin/env python3
"""Controls for the source-bound ordinary notification journey, without a server."""
import contextlib
import copy
import importlib.util
import io
from pathlib import Path
from types import SimpleNamespace
import unittest
from unittest import mock
ROOT = Path(__file__).resolve().parents[2]
SPEC = importlib.util.spec_from_file_location('notification_runtime', ROOT / 'scripts/check_notification_runtime.py')
RUNTIME = importlib.util.module_from_spec(SPEC)
SPEC.loader.exec_module(RUNTIME)
SHA = 'a' * 40
class FakeResponse:
def __init__(self, raw=b'{}', status=200):
self.raw, self.status = raw, status
self.limit = None
def __enter__(self):
return self
def __exit__(self, *_):
return None
def read(self, limit):
self.limit = limit
return self.raw
class NotificationRuntimeControls(unittest.TestCase):
def valid_row(self):
return {'path': '/owned-alert', 'body': {'resourceName': 'owned.example.test',
'instance': RUNTIME.PUBLIC_URL, 'event': 'alert', 'type': 'cpu'}}
def journey(self):
return RUNTIME.Journey(Path('/unused-binary'), Path('/unused-data'), io.BytesIO(), None)
def test_current_ordinary_event_not_resolution_or_invented_firing(self):
source = (ROOT / 'internal/notifications/notifications.go').read_text()
prepare = source.split('func (n *NotificationManager) prepareWebhookData(', 1)[1]
self.assertRegex(prepare, r'Event:\s+"alert",')
self.assertEqual(RUNTIME.ORDINARY_EVENT, 'alert')
row = self.valid_row()
self.assertTrue(all(RUNTIME.delivery_checks(row, 'owned.example.test').values()))
for event in ('firing', 'resolved', None):
with self.subTest(event=event):
row['body']['event'] = event
self.assertFalse(RUNTIME.delivery_checks(row, 'owned.example.test')['ordinary_event'])
def test_all_five_delivery_conditions_are_independent(self):
cases = [('path', '/foreign', 'owned_path'),
('resourceName', 'other.example.test', 'resource_identity'),
('instance', 'https://other.example.test', 'saved_public_url'),
('event', 'resolved', 'ordinary_event'),
('type', 'memory', 'cpu_metric')]
for field, value, failing in cases:
with self.subTest(field=field):
row = copy.deepcopy(self.valid_row())
target = row if field == 'path' else row['body']
target[field] = value
checks = RUNTIME.delivery_checks(row, 'owned.example.test')
self.assertEqual([key for key, passed in checks.items() if not passed], [failing])
def test_uncertain_mutation_is_never_replayed_and_keeps_request_identity(self):
journey = self.journey()
client = mock.Mock()
client.open.side_effect = TimeoutError('private provider text must not be printed')
journey.client = client
with contextlib.redirect_stdout(io.StringIO()) as output:
with self.assertRaises(TimeoutError):
journey.request('/api/alerts/config', {'privateValue': 'do-not-log'}, method='PUT')
client.open.assert_called_once()
self.assertEqual(journey.active_request, {'path': '/api/alerts/config', 'method': 'PUT',
'authentication': 'session'})
self.assertEqual(journey.requests, [])
self.assertNotIn('do-not-log', output.getvalue())
self.assertNotIn('private provider text', output.getvalue())
def test_expected_refusal_does_not_decode_or_print_response_body(self):
journey = self.journey()
response = FakeResponse(b'private rejection detail', 403)
journey.client = mock.Mock()
journey.client.open.return_value = response
with contextlib.redirect_stdout(io.StringIO()) as output:
self.assertIsNone(journey.request('/api/system/settings', expected=403))
self.assertEqual(response.limit, RUNTIME.LIMIT + 1)
self.assertNotIn('private rejection detail', output.getvalue())
self.assertEqual(journey.requests[-1]['status'], 403)
self.assertIsNone(journey.active_request)
def test_oversized_success_stops_after_one_read(self):
journey = self.journey()
response = FakeResponse(b'x' * (RUNTIME.LIMIT + 1))
journey.client = mock.Mock()
journey.client.open.return_value = response
with contextlib.redirect_stdout(io.StringIO()):
with self.assertRaisesRegex(RUNTIME.Failure, 'byte bound'):
journey.request('/api/resources')
journey.client.open.assert_called_once()
def test_settings_patch_preserves_and_tests_deployment_telemetry_lock(self):
journey = self.journey()
journey.request = mock.Mock()
with contextlib.redirect_stdout(io.StringIO()):
journey.save_system_policy()
self.assertEqual(journey.request.call_args_list, [
mock.call('/api/system/settings/update', {'telemetryEnabled': False}, expected=409),
mock.call('/api/system/settings/update', {'publicURL': RUNTIME.PUBLIC_URL,
'webhookAllowedPrivateCIDRs': '127.0.0.1/32'})])
self.assertEqual(journey.phases, ['deployment-telemetry-lock-preserved'])
handler = (ROOT / 'internal/api/system_settings.go').read_text()
self.assertIn('rawRequest["telemetryEnabled"]; ok && h.config.EnvOverrides["PULSE_TELEMETRY"]', handler)
def test_policy_readback_requires_disabled_locked_telemetry(self):
system = {'publicURL': RUNTIME.PUBLIC_URL, 'webhookAllowedPrivateCIDRs': '127.0.0.1/32',
'telemetryEnabled': False, 'envOverrides': {'PULSE_TELEMETRY': True}}
alerts = {'enabled': True, 'activationState': 'active',
'agentDefaults': {'cpu': {'trigger': 80}}}
for telemetry, lock, allowed in [(False, True, True), (True, True, False),
(None, True, False), (False, False, False)]:
with self.subTest(telemetry=telemetry, lock=lock):
journey = self.journey()
settings = copy.deepcopy(system)
settings['telemetryEnabled'], settings['envOverrides']['PULSE_TELEMETRY'] = telemetry, lock
journey.request = mock.Mock(side_effect=[settings, alerts])
if allowed:
journey.assert_settings()
else:
with self.assertRaisesRegex(RUNTIME.Failure, 'telemetry restriction'):
journey.assert_settings()
def test_only_clean_matching_checkout_and_binary_admitted(self):
valid = '\tbuild\tvcs=git\n\tbuild\tvcs.revision=' + SHA + '\n\tbuild\tvcs.modified=false\n'
cases = [(SHA, 0, valid, True),
('b' * 40, 0, valid, False),
(SHA, 1, valid, False),
(SHA, 0, valid.replace('modified=false', 'modified=true'), False),
(SHA, 0, valid.replace(SHA, 'b' * 40), False),
(SHA, 0, valid.replace('vcs=git', 'vcs=hg'), False),
(SHA, 0, '', False)]
for actual, dirty, binary, allowed in cases:
with self.subTest(actual=actual, dirty=dirty, binary=binary):
with mock.patch.object(RUNTIME.subprocess, 'check_output', side_effect=[actual, binary]), \
mock.patch.object(RUNTIME.subprocess, 'run', return_value=SimpleNamespace(returncode=dirty)):
if allowed:
self.assertEqual(RUNTIME.source_binding(Path('/owned-binary'), SHA)['checkout_sha'], SHA)
else:
with self.assertRaises(RUNTIME.Failure):
RUNTIME.source_binding(Path('/owned-binary'), SHA)
def test_malformed_source_identity_stops_before_commands(self):
with mock.patch.object(RUNTIME.subprocess, 'check_output') as command:
with self.assertRaises(RUNTIME.Failure):
RUNTIME.source_binding(Path('/owned-binary'), 'main')
command.assert_not_called()
def test_owned_child_has_no_ambient_credentials_or_telemetry(self):
journey = self.journey()
child = mock.Mock()
child.poll.return_value = None
journey.request = mock.Mock(return_value=None)
with mock.patch.dict(RUNTIME.os.environ, {'PATH': '/usr/bin', 'AMBIENT_SECRET': 'not-forwarded'}, clear=True), \
mock.patch.object(RUNTIME, 'free_port', return_value=7655), \
mock.patch.object(RUNTIME.subprocess, 'Popen', return_value=child) as launch:
journey.start()
env = launch.call_args.kwargs['env']
self.assertNotIn('AMBIENT_SECRET', env)
self.assertEqual(env['PULSE_TELEMETRY'], 'false')
self.assertEqual(env['PULSE_MOCK_MODE'], 'false')
self.assertEqual(env['DISCOVERY_ENABLED'], 'false')
self.assertEqual(env['BIND_ADDRESS'], '127.0.0.1')
self.assertTrue(launch.call_args.kwargs['start_new_session'])
def test_ci_reuses_the_real_embed_with_exact_source_and_attempt_receipt(self):
workflow = (ROOT / '.github/workflows/build-and-test.yml').read_text()
section = workflow.split('- name: Check ordinary notification policy and restart', 1)[1]
section = section.split('# An audit service failure', 1)[0]
self.assertIn('timeout-minutes: 3', section)
self.assertIn('--binary "$RUNNER_TEMP/pulse-real-embed"', section)
self.assertIn('--expected-source-sha "${{ github.sha }}"', section)
self.assertIn('--receipt "$RUNNER_TEMP/pulse-notification-runtime.json"', section)
self.assertIn('if: always()', section)
self.assertIn('notification-runtime-${{ github.run_id }}-${{ github.run_attempt }}', section)
self.assertNotIn('sudo', section)
self.assertNotIn('go build', section)
if __name__ == '__main__':
unittest.main(verbosity=2)