feat(server): Phase 1-2: add runtime control version history - #173

Merged
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1
Apr 22, 2026
Merged

feat(server): Phase 1-2: add runtime control version history#173
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1

Conversation

@lan17

@lan17lan17 commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Summary

This is Phase 1 of the control lifecycle work. It takes control_versions from migration-only/backfill state into active runtime use by recording new version rows on control mutations and exposing version history through read APIs.

This PR is stacked on top of #172.
Please review it against feature/control-phase-0, not against main.

Design doc and implementation plan: https://gist.github.com/lan17/6a08282243576f096626bb10996c024b

What changed

The server now records a version row whenever a control is created, updated, patched, or soft-deleted. Those writes happen in the same transaction as the control mutation, so the live control row and its latest audit snapshot stay in sync.

To keep that logic in one place, this PR introduces a ControlService that owns active-control lookup, version creation, and version-history reads. The control endpoints now use that service instead of each path reaching into the database on its own, and the agent/policy association endpoints use the same service for active-control existence checks.

This PR also adds two version history endpoints:

  • GET /api/v1/controls/{control_id}/versions
  • GET /api/v1/controls/{control_id}/versions/{version_num}

Those endpoints follow the repo's cursor-pagination conventions. The list endpoint returns summaries only, while the detail endpoint returns the full stored snapshot for audit and diffing.

The shared API models, Python SDK wrapper, and generated TypeScript SDK were updated to match the new endpoints and response shapes.

Review follow-up

A review-loop pass found a concurrency hole in version number allocation and one misleading error mapping on PATCH /controls/{id}. This branch now serializes version creation with a row-level control lock and only reports CONTROL_NAME_CONFLICT for actual control-name uniqueness failures.

Why this shape

Phase 0 created the schema and backfilled history, but runtime writes still were not participating in version tracking. That left control_versions useful for migration cleanup, but not yet trustworthy as the live audit trail.

This PR closes that gap before the later store/clone work. It also establishes ControlService as the boundary for control persistence concerns, which keeps the next phase from duplicating versioning and lookup logic again.

Reviewer notes

The important areas to look at are:

  • version-row creation on all control mutation paths
  • ControlService transaction behavior, row locking, and snapshot shape
  • version history endpoint pagination and deleted-control behavior
  • the shared-model / SDK surface added for version-history reads

Validation

  • make check
  • make openapi-spec-check
  • make sdk-ts-generate
  • make sdk-ts-overlay-test
  • make sdk-ts-name-check
  • targeted server slice for control/version endpoints
  • targeted Python SDK controls wrapper slice

@lan17
lan17 marked this pull request as ready for review April 16, 2026 03:48
@codecov

codecovBot commented Apr 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.86040% with 4 lines in your changes missing coverage. Please review.

Files with missing linesPatch %Lines
...ver/src/agent_control_server/endpoints/controls.py96.87%2 Missing ⚠️
...rver/src/agent_control_server/services/controls.py99.14%2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@lan17lan17 changed the title feat(server): add runtime control version historyfeat(server): implement phase 1 control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): implement phase 1 control version historyfeat(server): add runtime control version history (phase 1)Apr 16, 2026
@lan17lan17 changed the title feat(server): add runtime control version history (phase 1)feat(server): Phase 1: add runtime control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): Phase 1: add runtime control version historyfeat(server): Phase 1-2: add runtime control version historyApr 16, 2026
@lan17
lan17 merged commit bc09a1a into feature/control-phase-0Apr 22, 2026
5 checks passed
@lan17
lan17 deleted the feature/control-phase-1 branch April 22, 2026 02:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@lan17
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(server): Phase 1-2: add runtime control version history - #173

Merged
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1
Apr 22, 2026
Merged

feat(server): Phase 1-2: add runtime control version history#173
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1

Conversation

@lan17

@lan17lan17 commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Summary

This is Phase 1 of the control lifecycle work. It takes control_versions from migration-only/backfill state into active runtime use by recording new version rows on control mutations and exposing version history through read APIs.

This PR is stacked on top of #172.
Please review it against feature/control-phase-0, not against main.

Design doc and implementation plan: https://gist.github.com/lan17/6a08282243576f096626bb10996c024b

What changed

The server now records a version row whenever a control is created, updated, patched, or soft-deleted. Those writes happen in the same transaction as the control mutation, so the live control row and its latest audit snapshot stay in sync.

To keep that logic in one place, this PR introduces a ControlService that owns active-control lookup, version creation, and version-history reads. The control endpoints now use that service instead of each path reaching into the database on its own, and the agent/policy association endpoints use the same service for active-control existence checks.

This PR also adds two version history endpoints:

  • GET /api/v1/controls/{control_id}/versions
  • GET /api/v1/controls/{control_id}/versions/{version_num}

Those endpoints follow the repo's cursor-pagination conventions. The list endpoint returns summaries only, while the detail endpoint returns the full stored snapshot for audit and diffing.

The shared API models, Python SDK wrapper, and generated TypeScript SDK were updated to match the new endpoints and response shapes.

Review follow-up

A review-loop pass found a concurrency hole in version number allocation and one misleading error mapping on PATCH /controls/{id}. This branch now serializes version creation with a row-level control lock and only reports CONTROL_NAME_CONFLICT for actual control-name uniqueness failures.

Why this shape

Phase 0 created the schema and backfilled history, but runtime writes still were not participating in version tracking. That left control_versions useful for migration cleanup, but not yet trustworthy as the live audit trail.

This PR closes that gap before the later store/clone work. It also establishes ControlService as the boundary for control persistence concerns, which keeps the next phase from duplicating versioning and lookup logic again.

Reviewer notes

The important areas to look at are:

  • version-row creation on all control mutation paths
  • ControlService transaction behavior, row locking, and snapshot shape
  • version history endpoint pagination and deleted-control behavior
  • the shared-model / SDK surface added for version-history reads

Validation

  • make check
  • make openapi-spec-check
  • make sdk-ts-generate
  • make sdk-ts-overlay-test
  • make sdk-ts-name-check
  • targeted server slice for control/version endpoints
  • targeted Python SDK controls wrapper slice

@lan17
lan17 marked this pull request as ready for review April 16, 2026 03:48
@codecov

codecovBot commented Apr 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.86040% with 4 lines in your changes missing coverage. Please review.

Files with missing linesPatch %Lines
...ver/src/agent_control_server/endpoints/controls.py96.87%2 Missing ⚠️
...rver/src/agent_control_server/services/controls.py99.14%2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@lan17lan17 changed the title feat(server): add runtime control version historyfeat(server): implement phase 1 control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): implement phase 1 control version historyfeat(server): add runtime control version history (phase 1)Apr 16, 2026
@lan17lan17 changed the title feat(server): add runtime control version history (phase 1)feat(server): Phase 1: add runtime control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): Phase 1: add runtime control version historyfeat(server): Phase 1-2: add runtime control version historyApr 16, 2026
@lan17
lan17 merged commit bc09a1a into feature/control-phase-0Apr 22, 2026
5 checks passed
@lan17
lan17 deleted the feature/control-phase-1 branch April 22, 2026 02:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@lan17
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(server): Phase 1-2: add runtime control version history - #173

Merged
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1
Apr 22, 2026
Merged

feat(server): Phase 1-2: add runtime control version history#173
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1

Conversation

@lan17

@lan17lan17 commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Summary

This is Phase 1 of the control lifecycle work. It takes control_versions from migration-only/backfill state into active runtime use by recording new version rows on control mutations and exposing version history through read APIs.

This PR is stacked on top of #172.
Please review it against feature/control-phase-0, not against main.

Design doc and implementation plan: https://gist.github.com/lan17/6a08282243576f096626bb10996c024b

What changed

The server now records a version row whenever a control is created, updated, patched, or soft-deleted. Those writes happen in the same transaction as the control mutation, so the live control row and its latest audit snapshot stay in sync.

To keep that logic in one place, this PR introduces a ControlService that owns active-control lookup, version creation, and version-history reads. The control endpoints now use that service instead of each path reaching into the database on its own, and the agent/policy association endpoints use the same service for active-control existence checks.

This PR also adds two version history endpoints:

  • GET /api/v1/controls/{control_id}/versions
  • GET /api/v1/controls/{control_id}/versions/{version_num}

Those endpoints follow the repo's cursor-pagination conventions. The list endpoint returns summaries only, while the detail endpoint returns the full stored snapshot for audit and diffing.

The shared API models, Python SDK wrapper, and generated TypeScript SDK were updated to match the new endpoints and response shapes.

Review follow-up

A review-loop pass found a concurrency hole in version number allocation and one misleading error mapping on PATCH /controls/{id}. This branch now serializes version creation with a row-level control lock and only reports CONTROL_NAME_CONFLICT for actual control-name uniqueness failures.

Why this shape

Phase 0 created the schema and backfilled history, but runtime writes still were not participating in version tracking. That left control_versions useful for migration cleanup, but not yet trustworthy as the live audit trail.

This PR closes that gap before the later store/clone work. It also establishes ControlService as the boundary for control persistence concerns, which keeps the next phase from duplicating versioning and lookup logic again.

Reviewer notes

The important areas to look at are:

  • version-row creation on all control mutation paths
  • ControlService transaction behavior, row locking, and snapshot shape
  • version history endpoint pagination and deleted-control behavior
  • the shared-model / SDK surface added for version-history reads

Validation

  • make check
  • make openapi-spec-check
  • make sdk-ts-generate
  • make sdk-ts-overlay-test
  • make sdk-ts-name-check
  • targeted server slice for control/version endpoints
  • targeted Python SDK controls wrapper slice

@lan17
lan17 marked this pull request as ready for review April 16, 2026 03:48
@codecov

codecovBot commented Apr 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.86040% with 4 lines in your changes missing coverage. Please review.

Files with missing linesPatch %Lines
...ver/src/agent_control_server/endpoints/controls.py96.87%2 Missing ⚠️
...rver/src/agent_control_server/services/controls.py99.14%2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@lan17lan17 changed the title feat(server): add runtime control version historyfeat(server): implement phase 1 control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): implement phase 1 control version historyfeat(server): add runtime control version history (phase 1)Apr 16, 2026
@lan17lan17 changed the title feat(server): add runtime control version history (phase 1)feat(server): Phase 1: add runtime control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): Phase 1: add runtime control version historyfeat(server): Phase 1-2: add runtime control version historyApr 16, 2026
@lan17
lan17 merged commit bc09a1a into feature/control-phase-0Apr 22, 2026
5 checks passed
@lan17
lan17 deleted the feature/control-phase-1 branch April 22, 2026 02:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@lan17
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(server): Phase 1-2: add runtime control version history - #173

Merged
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1
Apr 22, 2026
Merged

feat(server): Phase 1-2: add runtime control version history#173
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1

Conversation

@lan17

@lan17lan17 commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Summary

This is Phase 1 of the control lifecycle work. It takes control_versions from migration-only/backfill state into active runtime use by recording new version rows on control mutations and exposing version history through read APIs.

This PR is stacked on top of #172.
Please review it against feature/control-phase-0, not against main.

Design doc and implementation plan: https://gist.github.com/lan17/6a08282243576f096626bb10996c024b

What changed

The server now records a version row whenever a control is created, updated, patched, or soft-deleted. Those writes happen in the same transaction as the control mutation, so the live control row and its latest audit snapshot stay in sync.

To keep that logic in one place, this PR introduces a ControlService that owns active-control lookup, version creation, and version-history reads. The control endpoints now use that service instead of each path reaching into the database on its own, and the agent/policy association endpoints use the same service for active-control existence checks.

This PR also adds two version history endpoints:

  • GET /api/v1/controls/{control_id}/versions
  • GET /api/v1/controls/{control_id}/versions/{version_num}

Those endpoints follow the repo's cursor-pagination conventions. The list endpoint returns summaries only, while the detail endpoint returns the full stored snapshot for audit and diffing.

The shared API models, Python SDK wrapper, and generated TypeScript SDK were updated to match the new endpoints and response shapes.

Review follow-up

A review-loop pass found a concurrency hole in version number allocation and one misleading error mapping on PATCH /controls/{id}. This branch now serializes version creation with a row-level control lock and only reports CONTROL_NAME_CONFLICT for actual control-name uniqueness failures.

Why this shape

Phase 0 created the schema and backfilled history, but runtime writes still were not participating in version tracking. That left control_versions useful for migration cleanup, but not yet trustworthy as the live audit trail.

This PR closes that gap before the later store/clone work. It also establishes ControlService as the boundary for control persistence concerns, which keeps the next phase from duplicating versioning and lookup logic again.

Reviewer notes

The important areas to look at are:

  • version-row creation on all control mutation paths
  • ControlService transaction behavior, row locking, and snapshot shape
  • version history endpoint pagination and deleted-control behavior
  • the shared-model / SDK surface added for version-history reads

Validation

  • make check
  • make openapi-spec-check
  • make sdk-ts-generate
  • make sdk-ts-overlay-test
  • make sdk-ts-name-check
  • targeted server slice for control/version endpoints
  • targeted Python SDK controls wrapper slice

@lan17
lan17 marked this pull request as ready for review April 16, 2026 03:48
@codecov

codecovBot commented Apr 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.86040% with 4 lines in your changes missing coverage. Please review.

Files with missing linesPatch %Lines
...ver/src/agent_control_server/endpoints/controls.py96.87%2 Missing ⚠️
...rver/src/agent_control_server/services/controls.py99.14%2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@lan17lan17 changed the title feat(server): add runtime control version historyfeat(server): implement phase 1 control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): implement phase 1 control version historyfeat(server): add runtime control version history (phase 1)Apr 16, 2026
@lan17lan17 changed the title feat(server): add runtime control version history (phase 1)feat(server): Phase 1: add runtime control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): Phase 1: add runtime control version historyfeat(server): Phase 1-2: add runtime control version historyApr 16, 2026
@lan17
lan17 merged commit bc09a1a into feature/control-phase-0Apr 22, 2026
5 checks passed
@lan17
lan17 deleted the feature/control-phase-1 branch April 22, 2026 02:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@lan17
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(server): Phase 1-2: add runtime control version history - #173

Merged
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1
Apr 22, 2026
Merged

feat(server): Phase 1-2: add runtime control version history#173
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1

Conversation

@lan17

@lan17lan17 commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Summary

This is Phase 1 of the control lifecycle work. It takes control_versions from migration-only/backfill state into active runtime use by recording new version rows on control mutations and exposing version history through read APIs.

This PR is stacked on top of #172.
Please review it against feature/control-phase-0, not against main.

Design doc and implementation plan: https://gist.github.com/lan17/6a08282243576f096626bb10996c024b

What changed

The server now records a version row whenever a control is created, updated, patched, or soft-deleted. Those writes happen in the same transaction as the control mutation, so the live control row and its latest audit snapshot stay in sync.

To keep that logic in one place, this PR introduces a ControlService that owns active-control lookup, version creation, and version-history reads. The control endpoints now use that service instead of each path reaching into the database on its own, and the agent/policy association endpoints use the same service for active-control existence checks.

This PR also adds two version history endpoints:

  • GET /api/v1/controls/{control_id}/versions
  • GET /api/v1/controls/{control_id}/versions/{version_num}

Those endpoints follow the repo's cursor-pagination conventions. The list endpoint returns summaries only, while the detail endpoint returns the full stored snapshot for audit and diffing.

The shared API models, Python SDK wrapper, and generated TypeScript SDK were updated to match the new endpoints and response shapes.

Review follow-up

A review-loop pass found a concurrency hole in version number allocation and one misleading error mapping on PATCH /controls/{id}. This branch now serializes version creation with a row-level control lock and only reports CONTROL_NAME_CONFLICT for actual control-name uniqueness failures.

Why this shape

Phase 0 created the schema and backfilled history, but runtime writes still were not participating in version tracking. That left control_versions useful for migration cleanup, but not yet trustworthy as the live audit trail.

This PR closes that gap before the later store/clone work. It also establishes ControlService as the boundary for control persistence concerns, which keeps the next phase from duplicating versioning and lookup logic again.

Reviewer notes

The important areas to look at are:

  • version-row creation on all control mutation paths
  • ControlService transaction behavior, row locking, and snapshot shape
  • version history endpoint pagination and deleted-control behavior
  • the shared-model / SDK surface added for version-history reads

Validation

  • make check
  • make openapi-spec-check
  • make sdk-ts-generate
  • make sdk-ts-overlay-test
  • make sdk-ts-name-check
  • targeted server slice for control/version endpoints
  • targeted Python SDK controls wrapper slice

@lan17
lan17 marked this pull request as ready for review April 16, 2026 03:48
@codecov

codecovBot commented Apr 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.86040% with 4 lines in your changes missing coverage. Please review.

Files with missing linesPatch %Lines
...ver/src/agent_control_server/endpoints/controls.py96.87%2 Missing ⚠️
...rver/src/agent_control_server/services/controls.py99.14%2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@lan17lan17 changed the title feat(server): add runtime control version historyfeat(server): implement phase 1 control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): implement phase 1 control version historyfeat(server): add runtime control version history (phase 1)Apr 16, 2026
@lan17lan17 changed the title feat(server): add runtime control version history (phase 1)feat(server): Phase 1: add runtime control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): Phase 1: add runtime control version historyfeat(server): Phase 1-2: add runtime control version historyApr 16, 2026
@lan17
lan17 merged commit bc09a1a into feature/control-phase-0Apr 22, 2026
5 checks passed
@lan17
lan17 deleted the feature/control-phase-1 branch April 22, 2026 02:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@lan17
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(server): Phase 1-2: add runtime control version history - #173

Merged
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1
Apr 22, 2026
Merged

feat(server): Phase 1-2: add runtime control version history#173
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1

Conversation

@lan17

@lan17lan17 commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Summary

This is Phase 1 of the control lifecycle work. It takes control_versions from migration-only/backfill state into active runtime use by recording new version rows on control mutations and exposing version history through read APIs.

This PR is stacked on top of #172.
Please review it against feature/control-phase-0, not against main.

Design doc and implementation plan: https://gist.github.com/lan17/6a08282243576f096626bb10996c024b

What changed

The server now records a version row whenever a control is created, updated, patched, or soft-deleted. Those writes happen in the same transaction as the control mutation, so the live control row and its latest audit snapshot stay in sync.

To keep that logic in one place, this PR introduces a ControlService that owns active-control lookup, version creation, and version-history reads. The control endpoints now use that service instead of each path reaching into the database on its own, and the agent/policy association endpoints use the same service for active-control existence checks.

This PR also adds two version history endpoints:

  • GET /api/v1/controls/{control_id}/versions
  • GET /api/v1/controls/{control_id}/versions/{version_num}

Those endpoints follow the repo's cursor-pagination conventions. The list endpoint returns summaries only, while the detail endpoint returns the full stored snapshot for audit and diffing.

The shared API models, Python SDK wrapper, and generated TypeScript SDK were updated to match the new endpoints and response shapes.

Review follow-up

A review-loop pass found a concurrency hole in version number allocation and one misleading error mapping on PATCH /controls/{id}. This branch now serializes version creation with a row-level control lock and only reports CONTROL_NAME_CONFLICT for actual control-name uniqueness failures.

Why this shape

Phase 0 created the schema and backfilled history, but runtime writes still were not participating in version tracking. That left control_versions useful for migration cleanup, but not yet trustworthy as the live audit trail.

This PR closes that gap before the later store/clone work. It also establishes ControlService as the boundary for control persistence concerns, which keeps the next phase from duplicating versioning and lookup logic again.

Reviewer notes

The important areas to look at are:

  • version-row creation on all control mutation paths
  • ControlService transaction behavior, row locking, and snapshot shape
  • version history endpoint pagination and deleted-control behavior
  • the shared-model / SDK surface added for version-history reads

Validation

  • make check
  • make openapi-spec-check
  • make sdk-ts-generate
  • make sdk-ts-overlay-test
  • make sdk-ts-name-check
  • targeted server slice for control/version endpoints
  • targeted Python SDK controls wrapper slice

@lan17
lan17 marked this pull request as ready for review April 16, 2026 03:48
@codecov

codecovBot commented Apr 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.86040% with 4 lines in your changes missing coverage. Please review.

Files with missing linesPatch %Lines
...ver/src/agent_control_server/endpoints/controls.py96.87%2 Missing ⚠️
...rver/src/agent_control_server/services/controls.py99.14%2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@lan17lan17 changed the title feat(server): add runtime control version historyfeat(server): implement phase 1 control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): implement phase 1 control version historyfeat(server): add runtime control version history (phase 1)Apr 16, 2026
@lan17lan17 changed the title feat(server): add runtime control version history (phase 1)feat(server): Phase 1: add runtime control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): Phase 1: add runtime control version historyfeat(server): Phase 1-2: add runtime control version historyApr 16, 2026
@lan17
lan17 merged commit bc09a1a into feature/control-phase-0Apr 22, 2026
5 checks passed
@lan17
lan17 deleted the feature/control-phase-1 branch April 22, 2026 02:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@lan17
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(server): Phase 1-2: add runtime control version history - #173

Merged
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1
Apr 22, 2026
Merged

feat(server): Phase 1-2: add runtime control version history#173
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1

Conversation

@lan17

@lan17lan17 commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Summary

This is Phase 1 of the control lifecycle work. It takes control_versions from migration-only/backfill state into active runtime use by recording new version rows on control mutations and exposing version history through read APIs.

This PR is stacked on top of #172.
Please review it against feature/control-phase-0, not against main.

Design doc and implementation plan: https://gist.github.com/lan17/6a08282243576f096626bb10996c024b

What changed

The server now records a version row whenever a control is created, updated, patched, or soft-deleted. Those writes happen in the same transaction as the control mutation, so the live control row and its latest audit snapshot stay in sync.

To keep that logic in one place, this PR introduces a ControlService that owns active-control lookup, version creation, and version-history reads. The control endpoints now use that service instead of each path reaching into the database on its own, and the agent/policy association endpoints use the same service for active-control existence checks.

This PR also adds two version history endpoints:

  • GET /api/v1/controls/{control_id}/versions
  • GET /api/v1/controls/{control_id}/versions/{version_num}

Those endpoints follow the repo's cursor-pagination conventions. The list endpoint returns summaries only, while the detail endpoint returns the full stored snapshot for audit and diffing.

The shared API models, Python SDK wrapper, and generated TypeScript SDK were updated to match the new endpoints and response shapes.

Review follow-up

A review-loop pass found a concurrency hole in version number allocation and one misleading error mapping on PATCH /controls/{id}. This branch now serializes version creation with a row-level control lock and only reports CONTROL_NAME_CONFLICT for actual control-name uniqueness failures.

Why this shape

Phase 0 created the schema and backfilled history, but runtime writes still were not participating in version tracking. That left control_versions useful for migration cleanup, but not yet trustworthy as the live audit trail.

This PR closes that gap before the later store/clone work. It also establishes ControlService as the boundary for control persistence concerns, which keeps the next phase from duplicating versioning and lookup logic again.

Reviewer notes

The important areas to look at are:

  • version-row creation on all control mutation paths
  • ControlService transaction behavior, row locking, and snapshot shape
  • version history endpoint pagination and deleted-control behavior
  • the shared-model / SDK surface added for version-history reads

Validation

  • make check
  • make openapi-spec-check
  • make sdk-ts-generate
  • make sdk-ts-overlay-test
  • make sdk-ts-name-check
  • targeted server slice for control/version endpoints
  • targeted Python SDK controls wrapper slice

@lan17
lan17 marked this pull request as ready for review April 16, 2026 03:48
@codecov

codecovBot commented Apr 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.86040% with 4 lines in your changes missing coverage. Please review.

Files with missing linesPatch %Lines
...ver/src/agent_control_server/endpoints/controls.py96.87%2 Missing ⚠️
...rver/src/agent_control_server/services/controls.py99.14%2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@lan17lan17 changed the title feat(server): add runtime control version historyfeat(server): implement phase 1 control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): implement phase 1 control version historyfeat(server): add runtime control version history (phase 1)Apr 16, 2026
@lan17lan17 changed the title feat(server): add runtime control version history (phase 1)feat(server): Phase 1: add runtime control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): Phase 1: add runtime control version historyfeat(server): Phase 1-2: add runtime control version historyApr 16, 2026
@lan17
lan17 merged commit bc09a1a into feature/control-phase-0Apr 22, 2026
5 checks passed
@lan17
lan17 deleted the feature/control-phase-1 branch April 22, 2026 02:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@lan17
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(server): Phase 1-2: add runtime control version history - #173

Merged
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1
Apr 22, 2026
Merged

feat(server): Phase 1-2: add runtime control version history#173
lan17 merged 5 commits into
feature/control-phase-0from
feature/control-phase-1

Conversation

@lan17

@lan17lan17 commented Apr 16, 2026

Copy link
Copy Markdown
Contributor

Summary

This is Phase 1 of the control lifecycle work. It takes control_versions from migration-only/backfill state into active runtime use by recording new version rows on control mutations and exposing version history through read APIs.

This PR is stacked on top of #172.
Please review it against feature/control-phase-0, not against main.

Design doc and implementation plan: https://gist.github.com/lan17/6a08282243576f096626bb10996c024b

What changed

The server now records a version row whenever a control is created, updated, patched, or soft-deleted. Those writes happen in the same transaction as the control mutation, so the live control row and its latest audit snapshot stay in sync.

To keep that logic in one place, this PR introduces a ControlService that owns active-control lookup, version creation, and version-history reads. The control endpoints now use that service instead of each path reaching into the database on its own, and the agent/policy association endpoints use the same service for active-control existence checks.

This PR also adds two version history endpoints:

  • GET /api/v1/controls/{control_id}/versions
  • GET /api/v1/controls/{control_id}/versions/{version_num}

Those endpoints follow the repo's cursor-pagination conventions. The list endpoint returns summaries only, while the detail endpoint returns the full stored snapshot for audit and diffing.

The shared API models, Python SDK wrapper, and generated TypeScript SDK were updated to match the new endpoints and response shapes.

Review follow-up

A review-loop pass found a concurrency hole in version number allocation and one misleading error mapping on PATCH /controls/{id}. This branch now serializes version creation with a row-level control lock and only reports CONTROL_NAME_CONFLICT for actual control-name uniqueness failures.

Why this shape

Phase 0 created the schema and backfilled history, but runtime writes still were not participating in version tracking. That left control_versions useful for migration cleanup, but not yet trustworthy as the live audit trail.

This PR closes that gap before the later store/clone work. It also establishes ControlService as the boundary for control persistence concerns, which keeps the next phase from duplicating versioning and lookup logic again.

Reviewer notes

The important areas to look at are:

  • version-row creation on all control mutation paths
  • ControlService transaction behavior, row locking, and snapshot shape
  • version history endpoint pagination and deleted-control behavior
  • the shared-model / SDK surface added for version-history reads

Validation

  • make check
  • make openapi-spec-check
  • make sdk-ts-generate
  • make sdk-ts-overlay-test
  • make sdk-ts-name-check
  • targeted server slice for control/version endpoints
  • targeted Python SDK controls wrapper slice

@lan17
lan17 marked this pull request as ready for review April 16, 2026 03:48
@codecov

codecovBot commented Apr 16, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 98.86040% with 4 lines in your changes missing coverage. Please review.

Files with missing linesPatch %Lines
...ver/src/agent_control_server/endpoints/controls.py96.87%2 Missing ⚠️
...rver/src/agent_control_server/services/controls.py99.14%2 Missing ⚠️

📢 Thoughts on this report? Let us know!

@lan17lan17 changed the title feat(server): add runtime control version historyfeat(server): implement phase 1 control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): implement phase 1 control version historyfeat(server): add runtime control version history (phase 1)Apr 16, 2026
@lan17lan17 changed the title feat(server): add runtime control version history (phase 1)feat(server): Phase 1: add runtime control version historyApr 16, 2026
@lan17lan17 changed the title feat(server): Phase 1: add runtime control version historyfeat(server): Phase 1-2: add runtime control version historyApr 16, 2026
@lan17
lan17 merged commit bc09a1a into feature/control-phase-0Apr 22, 2026
5 checks passed
@lan17
lan17 deleted the feature/control-phase-1 branch April 22, 2026 02:04
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@lan17