surf-agentic-base 0.3.0__tar.gz

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.
Files changed (163) hide show
  1. surf_agentic_base-0.3.0/.dockerignore +13 -0
  2. surf_agentic_base-0.3.0/.github/CODEOWNERS +2 -0
  3. surf_agentic_base-0.3.0/.github/ISSUE_TEMPLATE/bug_report.yml +29 -0
  4. surf_agentic_base-0.3.0/.github/ISSUE_TEMPLATE/config.yml +5 -0
  5. surf_agentic_base-0.3.0/.github/ISSUE_TEMPLATE/feature_request.yml +20 -0
  6. surf_agentic_base-0.3.0/.github/PULL_REQUEST_TEMPLATE.md +26 -0
  7. surf_agentic_base-0.3.0/.github/allowed_signers +5 -0
  8. surf_agentic_base-0.3.0/.github/dependabot.yml +10 -0
  9. surf_agentic_base-0.3.0/.github/release.yml +16 -0
  10. surf_agentic_base-0.3.0/.github/renovate.json5 +10 -0
  11. surf_agentic_base-0.3.0/.github/workflows/ci.yml +37 -0
  12. surf_agentic_base-0.3.0/.github/workflows/label.yml +89 -0
  13. surf_agentic_base-0.3.0/.github/workflows/release.yml +88 -0
  14. surf_agentic_base-0.3.0/.gitignore +73 -0
  15. surf_agentic_base-0.3.0/.markdownlint-cli2.jsonc +7 -0
  16. surf_agentic_base-0.3.0/.pre-commit-config.yaml +41 -0
  17. surf_agentic_base-0.3.0/CITATION.cff +25 -0
  18. surf_agentic_base-0.3.0/CODE_OF_CONDUCT.md +15 -0
  19. surf_agentic_base-0.3.0/CONTRIBUTING.md +92 -0
  20. surf_agentic_base-0.3.0/Dockerfile +52 -0
  21. surf_agentic_base-0.3.0/Justfile +32 -0
  22. surf_agentic_base-0.3.0/LICENSE +190 -0
  23. surf_agentic_base-0.3.0/PKG-INFO +144 -0
  24. surf_agentic_base-0.3.0/README.md +95 -0
  25. surf_agentic_base-0.3.0/SECURITY.md +26 -0
  26. surf_agentic_base-0.3.0/START_HERE.md +81 -0
  27. surf_agentic_base-0.3.0/charts/app/.helmignore +23 -0
  28. surf_agentic_base-0.3.0/charts/app/Chart.yaml +26 -0
  29. surf_agentic_base-0.3.0/charts/app/templates/NOTES.txt +22 -0
  30. surf_agentic_base-0.3.0/charts/app/templates/_helpers.tpl +83 -0
  31. surf_agentic_base-0.3.0/charts/app/templates/deployment.yaml +89 -0
  32. surf_agentic_base-0.3.0/charts/app/templates/hpa.yaml +17 -0
  33. surf_agentic_base-0.3.0/charts/app/templates/ingress.yaml +76 -0
  34. surf_agentic_base-0.3.0/charts/app/templates/networkpolicy.yaml +28 -0
  35. surf_agentic_base-0.3.0/charts/app/templates/poddisruptionbudget.yaml +13 -0
  36. surf_agentic_base-0.3.0/charts/app/templates/prometheusrule.yaml +32 -0
  37. surf_agentic_base-0.3.0/charts/app/templates/service.yaml +30 -0
  38. surf_agentic_base-0.3.0/charts/app/templates/serviceaccount.yaml +13 -0
  39. surf_agentic_base-0.3.0/charts/app/templates/servicemonitor.yaml +15 -0
  40. surf_agentic_base-0.3.0/charts/app/templates/tests/test-connection.yaml +15 -0
  41. surf_agentic_base-0.3.0/charts/app/values.yaml +208 -0
  42. surf_agentic_base-0.3.0/docs/ENGINEERING.md +189 -0
  43. surf_agentic_base-0.3.0/docs/architecture/boundaries.md +79 -0
  44. surf_agentic_base-0.3.0/docs/architecture/compliance.md +97 -0
  45. surf_agentic_base-0.3.0/docs/architecture/deployment-overlay.md +82 -0
  46. surf_agentic_base-0.3.0/docs/architecture/from-agentic-env.md +97 -0
  47. surf_agentic_base-0.3.0/docs/architecture/go-live-on-sdp.md +65 -0
  48. surf_agentic_base-0.3.0/docs/architecture/kubernetes.md +76 -0
  49. surf_agentic_base-0.3.0/docs/architecture/layering.md +77 -0
  50. surf_agentic_base-0.3.0/docs/architecture/move-plan.md +182 -0
  51. surf_agentic_base-0.3.0/docs/architecture/operational-traps.md +116 -0
  52. surf_agentic_base-0.3.0/docs/architecture/picture.md +125 -0
  53. surf_agentic_base-0.3.0/docs/architecture/picture.svg +81 -0
  54. surf_agentic_base-0.3.0/docs/architecture/process.md +55 -0
  55. surf_agentic_base-0.3.0/docs/architecture/proposals.md +69 -0
  56. surf_agentic_base-0.3.0/docs/architecture/reuse-ledger.md +204 -0
  57. surf_agentic_base-0.3.0/docs/decisions.md +143 -0
  58. surf_agentic_base-0.3.0/docs/include-readme.md +1 -0
  59. surf_agentic_base-0.3.0/docs/index.md +10 -0
  60. surf_agentic_base-0.3.0/docs/schemas/OutcomeRunFacet.json +24 -0
  61. surf_agentic_base-0.3.0/mkdocs.yml +30 -0
  62. surf_agentic_base-0.3.0/overlay.cfg +25 -0
  63. surf_agentic_base-0.3.0/pyproject.toml +244 -0
  64. surf_agentic_base-0.3.0/scripts/__init__.py +0 -0
  65. surf_agentic_base-0.3.0/scripts/assert_no_permitted_failures.py +112 -0
  66. surf_agentic_base-0.3.0/scripts/check_wheel_imports.py +97 -0
  67. surf_agentic_base-0.3.0/scripts/overlay.py +240 -0
  68. surf_agentic_base-0.3.0/setup.cfg +4 -0
  69. surf_agentic_base-0.3.0/src/agentic_base/__init__.py +0 -0
  70. surf_agentic_base-0.3.0/src/agentic_base/client.py +172 -0
  71. surf_agentic_base-0.3.0/src/agentic_base/code_policy/__init__.py +0 -0
  72. surf_agentic_base-0.3.0/src/agentic_base/code_policy/policy.py +122 -0
  73. surf_agentic_base-0.3.0/src/agentic_base/config.py +24 -0
  74. surf_agentic_base-0.3.0/src/agentic_base/db.py +42 -0
  75. surf_agentic_base-0.3.0/src/agentic_base/domain/__init__.py +1 -0
  76. surf_agentic_base-0.3.0/src/agentic_base/domain/epochs.py +187 -0
  77. surf_agentic_base-0.3.0/src/agentic_base/domain/integrity.py +135 -0
  78. surf_agentic_base-0.3.0/src/agentic_base/domain/outcomes.py +260 -0
  79. surf_agentic_base-0.3.0/src/agentic_base/domain/run_record.py +190 -0
  80. surf_agentic_base-0.3.0/src/agentic_base/domain/validity.py +254 -0
  81. surf_agentic_base-0.3.0/src/agentic_base/hpc/__init__.py +0 -0
  82. surf_agentic_base-0.3.0/src/agentic_base/hpc/clusters.py +165 -0
  83. surf_agentic_base-0.3.0/src/agentic_base/hpc/job_result.py +129 -0
  84. surf_agentic_base-0.3.0/src/agentic_base/hpc/profiles/lumi.yaml +74 -0
  85. surf_agentic_base-0.3.0/src/agentic_base/hpc/profiles/snellius.yaml +73 -0
  86. surf_agentic_base-0.3.0/src/agentic_base/limits.py +56 -0
  87. surf_agentic_base-0.3.0/src/agentic_base/llm/__init__.py +0 -0
  88. surf_agentic_base-0.3.0/src/agentic_base/llm/health.py +136 -0
  89. surf_agentic_base-0.3.0/src/agentic_base/llm/resilience.py +87 -0
  90. surf_agentic_base-0.3.0/src/agentic_base/main.py +66 -0
  91. surf_agentic_base-0.3.0/src/agentic_base/mcp/__init__.py +0 -0
  92. surf_agentic_base-0.3.0/src/agentic_base/mcp/server.py +266 -0
  93. surf_agentic_base-0.3.0/src/agentic_base/observability/__init__.py +0 -0
  94. surf_agentic_base-0.3.0/src/agentic_base/observability/conventions.py +183 -0
  95. surf_agentic_base-0.3.0/src/agentic_base/observability/tracing.py +109 -0
  96. surf_agentic_base-0.3.0/src/agentic_base/provenance/__init__.py +25 -0
  97. surf_agentic_base-0.3.0/src/agentic_base/provenance/emit.py +272 -0
  98. surf_agentic_base-0.3.0/src/agentic_base/py.typed +0 -0
  99. surf_agentic_base-0.3.0/src/agentic_base/recording.py +131 -0
  100. surf_agentic_base-0.3.0/src/agentic_base/routers/__init__.py +0 -0
  101. surf_agentic_base-0.3.0/src/agentic_base/routers/health.py +72 -0
  102. surf_agentic_base-0.3.0/src/agentic_base/routers/runs.py +157 -0
  103. surf_agentic_base-0.3.0/src/agentic_base/security/__init__.py +0 -0
  104. surf_agentic_base-0.3.0/src/agentic_base/security/netsec.py +258 -0
  105. surf_agentic_base-0.3.0/src/agentic_base/tools/__init__.py +0 -0
  106. surf_agentic_base-0.3.0/src/agentic_base/tools/types.py +139 -0
  107. surf_agentic_base-0.3.0/src/agentic_base/utils/__init__.py +1 -0
  108. surf_agentic_base-0.3.0/src/agentic_base/utils/logging.py +203 -0
  109. surf_agentic_base-0.3.0/src/surf_agentic_base.egg-info/PKG-INFO +144 -0
  110. surf_agentic_base-0.3.0/src/surf_agentic_base.egg-info/SOURCES.txt +161 -0
  111. surf_agentic_base-0.3.0/src/surf_agentic_base.egg-info/dependency_links.txt +1 -0
  112. surf_agentic_base-0.3.0/src/surf_agentic_base.egg-info/requires.txt +25 -0
  113. surf_agentic_base-0.3.0/src/surf_agentic_base.egg-info/scm_file_list.json +158 -0
  114. surf_agentic_base-0.3.0/src/surf_agentic_base.egg-info/scm_version.json +8 -0
  115. surf_agentic_base-0.3.0/src/surf_agentic_base.egg-info/top_level.txt +1 -0
  116. surf_agentic_base-0.3.0/tests/__init__.py +0 -0
  117. surf_agentic_base-0.3.0/tests/client/__init__.py +0 -0
  118. surf_agentic_base-0.3.0/tests/client/test_client.py +61 -0
  119. surf_agentic_base-0.3.0/tests/code_policy/__init__.py +0 -0
  120. surf_agentic_base-0.3.0/tests/code_policy/test_policy.py +50 -0
  121. surf_agentic_base-0.3.0/tests/conftest.py +26 -0
  122. surf_agentic_base-0.3.0/tests/domain/__init__.py +0 -0
  123. surf_agentic_base-0.3.0/tests/domain/test_epochs.py +141 -0
  124. surf_agentic_base-0.3.0/tests/domain/test_integrity.py +82 -0
  125. surf_agentic_base-0.3.0/tests/domain/test_outcomes.py +143 -0
  126. surf_agentic_base-0.3.0/tests/domain/test_run_record.py +71 -0
  127. surf_agentic_base-0.3.0/tests/domain/test_validity.py +178 -0
  128. surf_agentic_base-0.3.0/tests/hpc/__init__.py +0 -0
  129. surf_agentic_base-0.3.0/tests/hpc/test_clusters.py +129 -0
  130. surf_agentic_base-0.3.0/tests/hpc/test_job_result.py +74 -0
  131. surf_agentic_base-0.3.0/tests/lessons/__init__.py +0 -0
  132. surf_agentic_base-0.3.0/tests/lessons/test_a_null_from_a_mechanism_that_never_fired.py +67 -0
  133. surf_agentic_base-0.3.0/tests/llm/__init__.py +0 -0
  134. surf_agentic_base-0.3.0/tests/llm/test_health.py +70 -0
  135. surf_agentic_base-0.3.0/tests/llm/test_resilience.py +74 -0
  136. surf_agentic_base-0.3.0/tests/mcp/__init__.py +0 -0
  137. surf_agentic_base-0.3.0/tests/mcp/test_server.py +237 -0
  138. surf_agentic_base-0.3.0/tests/observability/__init__.py +0 -0
  139. surf_agentic_base-0.3.0/tests/observability/test_conventions.py +132 -0
  140. surf_agentic_base-0.3.0/tests/observability/test_tracing.py +110 -0
  141. surf_agentic_base-0.3.0/tests/provenance/__init__.py +0 -0
  142. surf_agentic_base-0.3.0/tests/provenance/test_emit.py +206 -0
  143. surf_agentic_base-0.3.0/tests/recording/__init__.py +0 -0
  144. surf_agentic_base-0.3.0/tests/recording/test_recording.py +91 -0
  145. surf_agentic_base-0.3.0/tests/routers/__init__.py +0 -0
  146. surf_agentic_base-0.3.0/tests/routers/test_health.py +15 -0
  147. surf_agentic_base-0.3.0/tests/routers/test_runs.py +107 -0
  148. surf_agentic_base-0.3.0/tests/security/__init__.py +0 -0
  149. surf_agentic_base-0.3.0/tests/security/test_netsec.py +264 -0
  150. surf_agentic_base-0.3.0/tests/test_chart.py +150 -0
  151. surf_agentic_base-0.3.0/tests/test_check_wheel_imports.py +55 -0
  152. surf_agentic_base-0.3.0/tests/test_config.py +7 -0
  153. surf_agentic_base-0.3.0/tests/test_limits.py +49 -0
  154. surf_agentic_base-0.3.0/tests/test_overlay_contract.py +145 -0
  155. surf_agentic_base-0.3.0/tests/test_permitted_failures.py +77 -0
  156. surf_agentic_base-0.3.0/tests/test_portable_surface.py +139 -0
  157. surf_agentic_base-0.3.0/tests/test_process.py +81 -0
  158. surf_agentic_base-0.3.0/tests/test_reuse_ledger.py +153 -0
  159. surf_agentic_base-0.3.0/tests/tools/__init__.py +0 -0
  160. surf_agentic_base-0.3.0/tests/tools/test_types.py +124 -0
  161. surf_agentic_base-0.3.0/tests/utils/__init__.py +0 -0
  162. surf_agentic_base-0.3.0/tests/utils/test_logging.py +97 -0
  163. surf_agentic_base-0.3.0/uv.lock +3992 -0
@@ -0,0 +1,13 @@
1
+ Dockerfile
2
+ **/.git
3
+ **/.gitignore
4
+ **/.gitmodules
5
+ **/.coverage
6
+ **/venv
7
+ **/.venv
8
+ **/.idea
9
+ **/.DS_Store
10
+ **/__pycache__
11
+ **/.mypy_cache
12
+ **/.pytest_cache
13
+ **/.ruff_cache
@@ -0,0 +1,2 @@
1
+ # Review requests route here. Kept to the maintainer while the repository has one.
2
+ * @saradamian
@@ -0,0 +1,29 @@
1
+ name: Bug report
2
+ description: Something behaves differently from what its documentation or its tests say.
3
+ labels: [bug]
4
+ body:
5
+ - type: textarea
6
+ id: what
7
+ attributes:
8
+ label: What happened, and what should have happened
9
+ description: The observed behaviour and the expected one. Quote the assertion or the documented claim it contradicts.
10
+ validations: { required: true }
11
+ - type: textarea
12
+ id: repro
13
+ attributes:
14
+ label: Minimal reproduction
15
+ description: The smallest input that shows it. A failing test is the best form.
16
+ render: python
17
+ validations: { required: true }
18
+ - type: input
19
+ id: version
20
+ attributes:
21
+ label: Version and Python
22
+ placeholder: "surf-agentic-base 0.1.0, Python 3.10.12"
23
+ validations: { required: true }
24
+ - type: checkboxes
25
+ id: warnings
26
+ attributes:
27
+ label: Verification
28
+ options:
29
+ - label: I ran the suite without `-W ignore`.
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Security report
4
+ url: https://github.com/saradamian/agentic-base/security/advisories/new
5
+ about: Report a vulnerability privately. Do not open a public issue for it.
@@ -0,0 +1,20 @@
1
+ name: Feature request
2
+ description: Something the base layer should carry, with the consumer that needs it.
3
+ labels: [enhancement]
4
+ body:
5
+ - type: textarea
6
+ id: consumer
7
+ attributes:
8
+ label: Which consumer needs this, and what does it do today instead
9
+ description: A base layer only carries what more than one consumer would otherwise write separately. Name the consumer.
10
+ validations: { required: true }
11
+ - type: textarea
12
+ id: existing
13
+ attributes:
14
+ label: What already exists that does this
15
+ description: Reuse, then buy, then build. Say what was checked and why it does not fit, or say nothing was found.
16
+ validations: { required: true }
17
+ - type: textarea
18
+ id: shape
19
+ attributes:
20
+ label: Proposed shape
@@ -0,0 +1,26 @@
1
+ ## What changes, and what does not
2
+
3
+ <!-- One behaviour per pull request. Say what is deliberately left for a follow-up. Do not oversell:
4
+ a reviewer who finds the claim broader than the diff stops trusting the rest. -->
5
+
6
+ Closes #
7
+
8
+ ## How it was verified
9
+
10
+ - [ ] `uv sync --group dev --extra service` then `ruff check`, `ruff format --check`, `mypy src tests`
11
+ - [ ] `pytest tests -q` **with no `-W` flag**. The repository treats warnings as errors; a run that ignores them is not a run.
12
+ - [ ] Any new guard was broken on purpose and seen to fail before it was trusted.
13
+
14
+ ## Portability
15
+
16
+ - [ ] If a module in `tests/test_portable_surface.py` changed: it still imports on 3.10 with only the four library dependencies.
17
+ - [ ] No site-specific value in code, chart defaults or the Dockerfile. Those belong in a deployment overlay.
18
+
19
+ ## Risk
20
+
21
+ | Question | Answer (write N/A explicitly) |
22
+ |---|---|
23
+ | Schema or stored record changed? | |
24
+ | Public interface changed? | |
25
+ | New dependency? | |
26
+ | Touches URL fetching, secrets, or code execution? | |
@@ -0,0 +1,5 @@
1
+ # Who may sign a release tag. `.github/workflows/release.yml` verifies the tag against this
2
+ # file before it builds anything, so a tag signed by a key absent from here does not release.
3
+ # Rotating a key means a commit, which is the point: the set of people who can cut a release
4
+ # is reviewable. The principal must equal the tagger email, or the signature does not match.
5
+ 26249073+saradamian@users.noreply.github.com ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIEj+dDHYzSSo0LOhb8OzHcyAumQsrXnzkH2yYwlKEKud
@@ -0,0 +1,10 @@
1
+ # Dependabot and Renovate split the work rather than both doing it. Dependabot owns security
2
+ # alerts and their fixes, and the workflow actions, because it is native here. Renovate owns
3
+ # ordinary version updates and lock-file maintenance, and its config disables the actions manager
4
+ # so the two never open the same pull request.
5
+ version: 2
6
+ updates:
7
+ - package-ecosystem: github-actions
8
+ directory: /
9
+ schedule: { interval: weekly }
10
+ labels: [dependencies]
@@ -0,0 +1,16 @@
1
+ # How generated release notes are grouped. Labels are applied to pull requests; the conventional
2
+ # commit prefix is what the pre-commit hook checks, the label is what the notes read.
3
+ changelog:
4
+ categories:
5
+ - title: Breaking
6
+ labels: [breaking]
7
+ - title: Features
8
+ labels: [feat, enhancement]
9
+ - title: Fixes
10
+ labels: [fix, bug]
11
+ - title: Documentation
12
+ labels: [documentation]
13
+ - title: Dependencies
14
+ labels: [dependencies]
15
+ - title: Other
16
+ labels: ["*"]
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3
+ "extends": ["config:recommended", ":semanticCommits"],
4
+ "lockFileMaintenance": { "enabled": true, "schedule": ["before 6am on monday"] },
5
+ "enabledManagers": ["pep621", "pip_requirements"],
6
+ "labels": ["dependencies"],
7
+ "packageRules": [
8
+ { "matchUpdateTypes": ["patch"], "automerge": false }
9
+ ]
10
+ }
@@ -0,0 +1,37 @@
1
+ # The gate this repository declares, run on both ends of the supported range. 3.10 is not
2
+ # decoration: it is the floor a consumer of this library runs on, and the portable-surface guards
3
+ # mean nothing unless something executes them there.
4
+ #
5
+ # Actions are pinned to commit hashes, not tags. A tag can be moved; a hash cannot.
6
+ name: ci
7
+
8
+ on:
9
+ push:
10
+ branches: [main]
11
+ pull_request:
12
+
13
+ permissions:
14
+ contents: read
15
+
16
+ concurrency:
17
+ group: ci-${{ github.ref }}
18
+ cancel-in-progress: true
19
+
20
+ jobs:
21
+ check:
22
+ runs-on: ubuntu-latest
23
+ strategy:
24
+ fail-fast: false
25
+ matrix:
26
+ python: ["3.10", "3.14"]
27
+ steps:
28
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
29
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
30
+ - run: uv lock --check
31
+ - run: uv sync --python ${{ matrix.python }} --group dev --extra service --extra provenance
32
+ - run: .venv/bin/ruff check src tests
33
+ - run: .venv/bin/ruff format --check src tests
34
+ - run: .venv/bin/mypy src tests
35
+ - run: .venv/bin/pytest tests -q --cov=src --cov-report=term
36
+ env:
37
+ DATABASE_URL: sqlite:///./ci.db
@@ -0,0 +1,89 @@
1
+ # The release notes group by pull request label (`.github/release.yml`). Nothing ever applied one:
2
+ # all fourteen labels existed, none of the first five pull requests carried any, so every entry
3
+ # would have been filed under "Other" and the grouping config could not do the thing it describes.
4
+ #
5
+ # The input is the conventional-commit prefix the commit hook already enforces, which squash
6
+ # merging puts on main as the commit subject. Deriving the label from the title keeps one source
7
+ # of truth rather than adding a second one for humans to keep in step.
8
+ #
9
+ # A prefix that maps to nothing is not an error. `chore`, `ci`, `test` and `refactor` belong under
10
+ # "Other", and this says so rather than failing.
11
+ #
12
+ # The derivation RECONCILES rather than adds. A title edited from `feat:` to `fix:` re-fires this
13
+ # workflow, and an add-only version would leave both labels in place; the notes then file the
14
+ # entry under whichever category matches first, which is the stale one. So each label this
15
+ # workflow can derive is either applied or removed on every run, and only those: a label a person
16
+ # added by hand, `enhancement` or `good first issue`, is never touched.
17
+ #
18
+ # `pull_request_target` is used because a fork's `pull_request` token cannot write labels. It runs
19
+ # with a writable token against the BASE repository, so this workflow deliberately checks out no
20
+ # code at all and reads the title through the environment rather than interpolating it into the
21
+ # script. Nothing from a fork is executed here.
22
+ name: label
23
+
24
+ on:
25
+ pull_request_target:
26
+ types: [opened, edited, reopened]
27
+
28
+ permissions:
29
+ pull-requests: write
30
+
31
+ jobs:
32
+ label:
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - name: derive the grouping label from the conventional-commit prefix
36
+ env:
37
+ GH_TOKEN: ${{ github.token }}
38
+ TITLE: ${{ github.event.pull_request.title }}
39
+ NUMBER: ${{ github.event.pull_request.number }}
40
+ REPO: ${{ github.repository }}
41
+ run: |
42
+ prefix=${TITLE%%:*}
43
+
44
+ breaking=""
45
+ case "$prefix" in *'!') breaking="breaking" ;; esac
46
+ prefix=${prefix%!}
47
+ prefix=${prefix%%(*}
48
+
49
+ case "$prefix" in
50
+ feat) label="feat" ;;
51
+ fix) label="fix" ;;
52
+ docs) label="documentation" ;;
53
+ deps|build) label="dependencies" ;;
54
+ *) label="" ;;
55
+ esac
56
+
57
+ wanted=""
58
+ [ -n "$label" ] && wanted="$label"
59
+ [ -n "$breaking" ] && wanted="$wanted $breaking"
60
+
61
+ if [ -z "$wanted" ]; then
62
+ echo "prefix '$prefix' derives no grouping label; the notes file this under Other"
63
+ fi
64
+
65
+ # Everything this workflow is allowed to decide. A label outside this set belongs to
66
+ # whoever put it there. There is no early exit above: an empty derivation still has to
67
+ # REMOVE a label an earlier title produced.
68
+ managed="breaking dependencies documentation feat fix"
69
+ held=" $(gh api "repos/$REPO/issues/$NUMBER/labels" --jq '.[].name' | tr '\n' ' ')"
70
+
71
+ for name in $managed; do
72
+ case " $wanted " in *" $name "*) want=1 ;; *) want=0 ;; esac
73
+ case "$held" in *" $name "*) have=1 ;; *) have=0 ;; esac
74
+
75
+ if [ "$want" = 1 ] && [ "$have" = 0 ]; then
76
+ # A JSON body on stdin, not `-f 'labels[]=x'`: that form is not understood by
77
+ # every gh version and 422s where it is not, failing this step for a reason that
78
+ # has nothing to do with the label.
79
+ printf '{"labels":["%s"]}' "$name" |
80
+ gh api "repos/$REPO/issues/$NUMBER/labels" --input - --silent
81
+ echo "added $name"
82
+ elif [ "$want" = 0 ] && [ "$have" = 1 ]; then
83
+ gh api -X DELETE "repos/$REPO/issues/$NUMBER/labels/$name" --silent
84
+ echo "removed $name, which the title no longer derives"
85
+ fi
86
+ done
87
+
88
+ set -- $managed
89
+ echo "reconciled $# derivable labels against the title"
@@ -0,0 +1,88 @@
1
+ # A tag `vX.Y.Z` on main produces a release. The version inside the built distribution comes
2
+ # from that same tag through setuptools-scm, so the two cannot disagree. Nothing is published to
3
+ # a package index from here; that is a separate decision with its own owner.
4
+ #
5
+ # Three things a consumer of a release can check now, and could not before:
6
+ #
7
+ # * the tag carries a signature from a key listed in `.github/allowed_signers`. Note that
8
+ # `gh release create --verify-tag`, at the end of this file, does NOT do this. It asserts the
9
+ # tag exists on the remote and reads no signature at all; the name invites the opposite
10
+ # reading. The rule starts at the next tag, because `v0.1.0` predates commit signing and the
11
+ # tag ruleset makes it immutable, so nobody can quietly re-sign it and close the gap;
12
+ # * the artifact installs and imports on the consumer's Python floor. The suite reads `src/`,
13
+ # so it cannot see a defect that exists only in the built wheel;
14
+ # * the artifact carries build provenance. Without it the chain of custody stops at the tag,
15
+ # and anyone with write access can replace a release asset with nothing to detect it.
16
+ name: release
17
+
18
+ on:
19
+ push:
20
+ tags: ["v*.*.*"]
21
+
22
+ permissions:
23
+ contents: write
24
+ # `attest-build-provenance` mints a short-lived signing certificate for this run and writes the
25
+ # attestation back to the repository. Both are required. With `contents: write` alone the build
26
+ # succeeds and the attest step fails at the end of it, having spent the whole job first.
27
+ id-token: write
28
+ attestations: write
29
+
30
+ jobs:
31
+ release:
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
35
+ with:
36
+ fetch-depth: 0
37
+ # The tag OBJECT, not just the commit it points at: the signature lives on the object.
38
+ fetch-tags: true
39
+
40
+ - name: the tag must be signed by a listed signer
41
+ run: |
42
+ git config gpg.ssh.allowedSignersFile .github/allowed_signers
43
+ git verify-tag "${GITHUB_REF_NAME}"
44
+
45
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
46
+ - run: uv build
47
+
48
+ - name: the built version must equal the tag
49
+ run: |
50
+ built=$(ls dist/*.tar.gz | sed -E 's|.*/surf_agentic_base-(.*)\.tar\.gz|\1|')
51
+ test "v$built" = "${GITHUB_REF_NAME}" || { echo "built $built, tag ${GITHUB_REF_NAME}"; exit 1; }
52
+
53
+ - name: the wheel must install and import on the consumer floor
54
+ run: |
55
+ uv venv --python 3.10 /tmp/floor
56
+ uv pip install --python /tmp/floor/bin/python --quiet dist/*.whl
57
+ python3 scripts/check_wheel_imports.py /tmp/floor/bin/python
58
+
59
+ - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
60
+ with:
61
+ subject-path: dist/*
62
+
63
+ - run: gh release create "${GITHUB_REF_NAME}" dist/* --generate-notes --verify-tag
64
+ env:
65
+ GH_TOKEN: ${{ github.token }}
66
+
67
+ # Trusted publishing: no token stored anywhere, PyPI accepts this workflow's OIDC identity
68
+ # for the project `surf-agentic-base` once that publisher is registered on pypi.org. A
69
+ # separate job so a publish failure is a red job on a release that already exists, never a
70
+ # release that silently does not.
71
+ publish:
72
+ needs: release
73
+ runs-on: ubuntu-latest
74
+ environment: pypi
75
+ permissions:
76
+ id-token: write
77
+ steps:
78
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
79
+ with:
80
+ fetch-depth: 0
81
+ fetch-tags: true
82
+ - uses: astral-sh/setup-uv@bec219d24cd3e171d82865faccec33120bb574f4 # v10.1.0
83
+ - run: uv build
84
+ - name: the built version must equal the tag, here too
85
+ run: |
86
+ built=$(ls dist/*.tar.gz | sed -E 's|.*/surf_agentic_base-(.*)\.tar\.gz|\1|')
87
+ test "v$built" = "${GITHUB_REF_NAME}" || { echo "built $built, tag ${GITHUB_REF_NAME}"; exit 1; }
88
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
@@ -0,0 +1,73 @@
1
+ # Python
2
+ venv
3
+ .venv
4
+ coverage.xml
5
+ .coverage
6
+ __pycache__/
7
+ .mypy_cache
8
+ .dmypy.json
9
+ .ruff_cache
10
+ .pytest_cache
11
+ test-results.xml
12
+ gl-code-quality-report.json
13
+ gl-code-quality-report-ruff.json
14
+ *.egg-info
15
+
16
+ # OS generated files
17
+ .DS_Store
18
+ .DS_Store?
19
+ ._*
20
+ .Spotlight-V100
21
+ .Trashes
22
+ ehthumbs.db
23
+ Thumbs.db
24
+
25
+ # User-specific stuff
26
+ .idea/**/workspace.xml
27
+ .idea/**/tasks.xml
28
+ .idea/**/usage.statistics.xml
29
+ .idea/**/dictionaries
30
+ .idea/**/shelf
31
+ .idea/**/*.iml
32
+ .idea/**/misc.xml
33
+ .idea/**/modules.xml
34
+
35
+ # AWS User-specific
36
+ .idea/**/aws.xml
37
+
38
+ # Generated files
39
+ .idea/**/contentModel.xml
40
+
41
+ # Sensitive or high-churn files
42
+ .idea/**/dataSources/
43
+ .idea/**/dataSources.ids
44
+ .idea/**/dataSources.local.xml
45
+ .idea/**/sqlDataSources.xml
46
+ .idea/**/dynamic.xml
47
+ .idea/**/uiDesigner.xml
48
+ .idea/**/dbnavigator.xml
49
+
50
+ # IntelliJ
51
+ out/
52
+
53
+ # JIRA plugin
54
+ atlassian-ide-plugin.xml
55
+
56
+ # Crashlytics plugin (for Android Studio and IntelliJ)
57
+ com_crashlytics_export_strings.xml
58
+ crashlytics.properties
59
+ crashlytics-build.properties
60
+ fabric.properties
61
+
62
+ # mkdocs
63
+ site
64
+
65
+ # Local databases. The service defaults to `sqlite:///./agentic-base.db` and the pipeline uses
66
+ # `./ci.db`, so both land in the working directory. The only `.db` entries here were Windows
67
+ # thumbnail caches by exact name, which meant a local run followed by `git add -A` could commit
68
+ # a database. Names, not just the two we happen to create today.
69
+ *.db
70
+ *.db-wal
71
+ *.db-shm
72
+ *.sqlite
73
+ *.sqlite3
@@ -0,0 +1,7 @@
1
+ // Rules can be found at: https://github.com/markdownlint/markdownlint/blob/main/docs/RULES.md
2
+ {
3
+ "config": {
4
+ // MD013 - Line length | https://github.com/markdownlint/markdownlint/blob/main/docs/RULES.md#md013---line-length
5
+ "MD013": false
6
+ }
7
+ }
@@ -0,0 +1,41 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v5.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ exclude: ^.*docs/.*\.md|.*foo.txt$
8
+ - id: check-yaml
9
+ args: [ '--multi' ]
10
+ exclude: ^charts/.*/templates/.*\.yaml|\.gitlab-ci\.yml$
11
+ - id: check-added-large-files
12
+ - repo: https://github.com/codespell-project/codespell
13
+ rev: v2.4.1
14
+ hooks:
15
+ - id: codespell
16
+ files: ^.*\.md$
17
+ - repo: https://github.com/astral-sh/ruff-pre-commit
18
+ rev: v0.12.7
19
+ hooks:
20
+ - id: ruff-check
21
+ - repo: https://github.com/DavidAnson/markdownlint-cli2
22
+ rev: v0.18.1
23
+ hooks:
24
+ - id: markdownlint-cli2
25
+ files: ^.*\.md$
26
+ exclude: ^.*docs/.*\.md$
27
+ - repo: https://github.com/hadolint/hadolint
28
+ rev: v2.12.0
29
+ hooks:
30
+ - id: hadolint-docker
31
+ - repo: https://github.com/gruntwork-io/pre-commit
32
+ rev: v0.1.30
33
+ hooks:
34
+ - id: helmlint
35
+ types: [ yaml ]
36
+ files: ^(charts/app/templates/.*\.yaml)$
37
+ - repo: https://github.com/commitizen-tools/commitizen
38
+ rev: v4.9.1
39
+ hooks:
40
+ - id: commitizen
41
+ stages: [ commit-msg ]
@@ -0,0 +1,25 @@
1
+ cff-version: 1.2.0
2
+ message: "If you use this software, please cite it using the metadata from this file."
3
+ type: software
4
+ title: "surf-agentic-base"
5
+ abstract: >-
6
+ A base layer for agentic work on research infrastructure. It records what actually ran with the
7
+ provenance needed to replay and audit it, adjudicates whether a comparison between two agent
8
+ configurations is sound enough to publish, and carries the primitives every consumer would
9
+ otherwise write separately and differently.
10
+ authors:
11
+ - family-names: Podareanu
12
+ given-names: Damian
13
+ affiliation: SURF
14
+ version: 0.3.0
15
+ date-released: "2026-09-12"
16
+ license: EUPL-1.2
17
+ repository-code: "https://github.com/saradamian/agentic-base"
18
+ keywords:
19
+ - agents
20
+ - provenance
21
+ - reproducibility
22
+ - evaluation
23
+ - model-context-protocol
24
+ - opentelemetry
25
+ - hpc
@@ -0,0 +1,15 @@
1
+ # Code of conduct
2
+
3
+ This project follows the [Contributor Covenant](https://www.contributor-covenant.org/version/2/1/code_of_conduct/),
4
+ version 2.1. It applies in every space this project runs: the repository, its issues and pull
5
+ requests, and any discussion of it that identifies as such.
6
+
7
+ In short: be considerate and constructive, accept responsibility and apologise when you get it
8
+ wrong, and take the interests of the community over your own convenience. Harassment,
9
+ discriminatory remarks, publishing others' private information, and sustained disruption are
10
+ not tolerated.
11
+
12
+ Report violations privately through the repository's security advisory channel at
13
+ <https://github.com/saradamian/agentic-base/security/advisories/new>, which reaches the
14
+ maintainers only. Reports are handled in confidence. The maintainers follow the Covenant's
15
+ enforcement ladder: correction, warning, temporary ban, permanent ban.
@@ -0,0 +1,92 @@
1
+ # Contributing
2
+
3
+ House rules, kept short so the next reader has less to hold in their head. Most exist because
4
+ something specific went wrong once; `docs/ENGINEERING.md` says what each one cost and which test
5
+ fails when it stops being true.
6
+
7
+ ## Where the reasoning lives
8
+
9
+ Code is terse. A docstring states purpose and constraints, what a reader needs to use or change
10
+ the thing safely. It does not carry history, justification, or an argument that the change is
11
+ correct; that goes in the commit message and, when it is a decision, in `docs/decisions.md` or
12
+ `docs/architecture/`. A reader who wants to know *why* looks there.
13
+
14
+ ## Two halves, one repository
15
+
16
+ The **library half** is what other projects import. It installs on Python 3.10 with pydantic,
17
+ pydantic-settings, httpx and PyYAML, and nothing else. Which modules are portable is a list in
18
+ `tests/test_portable_surface.py`, not a paragraph, and its guards fail when the list stops being
19
+ true. The **service half** is an optional extra. If your change makes a portable module import a
20
+ service dependency, the suite tells you.
21
+
22
+ ## Values that belong somewhere else
23
+
24
+ Anything that is a fact about *where* this runs rather than *what* it does, a registry, a
25
+ hostname, a pull secret, an environment name, does not go in code, chart defaults or the
26
+ Dockerfile. It goes in a deployment overlay outside this repository, and the overlay may only
27
+ add files, never modify ours; `overlay.cfg` declares which paths it owns and a guard refuses a
28
+ change here that creates one. The Dockerfile takes registries as build arguments for this reason.
29
+ The pattern and its tool are in `docs/architecture/deployment-overlay.md`.
30
+
31
+ ## Running the gate
32
+
33
+ ```
34
+ uv sync --group dev --extra service --extra provenance
35
+ .venv/bin/ruff check src tests
36
+ .venv/bin/ruff format --check src tests
37
+ .venv/bin/mypy src tests
38
+ .venv/bin/pytest tests -q
39
+ ```
40
+
41
+ Run pytest **without** `-W ignore`. The repository treats warnings as errors on purpose, and a
42
+ run that suppresses them is a run that cannot fail on the class of defect the policy exists for.
43
+ Two real defects reached the public gate that way in one day. If a warning must be tolerated,
44
+ scope a `filterwarnings` entry to that one message, name the upstream cause, and say when it can
45
+ be removed.
46
+
47
+ Continuous integration runs the same five steps on 3.10 and 3.14. If your machine has only one,
48
+ the runner is the other.
49
+
50
+ ## Tests
51
+
52
+ One test per behaviour, at the highest seam that can actually fail. Name the behaviour and the
53
+ condition: `test_a_verdict_from_a_degraded_instrument_is_not_citable`, not `test_citable`. A test
54
+ that no plausible bug would fail should be deleted. A new module needs a test module of the same
55
+ name in the same change; a guard checks.
56
+
57
+ **Break every guard you add.** Before trusting a check, make the thing it checks wrong and watch
58
+ it go red. A guard that cannot fail is worse than none, because it gets cited.
59
+
60
+ ## Commits and pull requests
61
+
62
+ Conventional commits: `type(scope): description`, with `feat`, `fix`, `test`, `refactor`,
63
+ `docs`, `chore`, `ci`, `perf`. The pre-commit hook enforces it; install it with
64
+ `pre-commit install --hook-type commit-msg`.
65
+
66
+ One behaviour per pull request. The description says what changes, what does not, and what is
67
+ deliberately left for later. It must not claim more than the diff delivers.
68
+
69
+ `main` accepts pull requests only, with the gate green on both interpreters, every review thread
70
+ resolved, and a linear history. There is no required reviewer count while the repository has one
71
+ maintainer; the gate is the reviewer. That changes the day there is a second.
72
+
73
+ ## Versioning and releases
74
+
75
+ Semantic versioning. The version is the git tag; nothing is edited to cut a release.
76
+
77
+ - `0.y.z` while the interface is settling. A minor bump may change the public interface, and it
78
+ is stated in the notes when it does. The import name is `agentic_base`; it was `app` until
79
+ `0.2.0`. The distribution name is `surf-agentic-base` since `0.3.0`, because `agentic-base` on
80
+ PyPI belongs to an unrelated project and a pin against it would have installed theirs.
81
+ - Patch releases fix without changing an interface.
82
+ - Tag `vX.Y.Z` on `main`. The release workflow builds the distribution, refuses if the built
83
+ version differs from the tag, and publishes a GitHub release with generated notes. Nothing goes
84
+ to a package index from here.
85
+ - `CITATION.cff` carries the version and the release date; update both in the same change that
86
+ tags.
87
+
88
+ ## Dependencies
89
+
90
+ Dependabot handles security alerts and the workflow actions. Renovate handles ordinary version
91
+ updates and lock-file maintenance. They are split so they never open the same pull request.
92
+ Actions are pinned by commit hash with the version in a trailing comment; bump both together.