subroutine 0.1.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.
- subroutine-0.1.0/.claude-plugin/marketplace.json +29 -0
- subroutine-0.1.0/.github/workflows/ci.yml +221 -0
- subroutine-0.1.0/.github/workflows/release.yml +178 -0
- subroutine-0.1.0/.gitignore +53 -0
- subroutine-0.1.0/CHANGELOG.md +76 -0
- subroutine-0.1.0/CLA.md +78 -0
- subroutine-0.1.0/CONTRIBUTING.md +131 -0
- subroutine-0.1.0/LICENSE +661 -0
- subroutine-0.1.0/PKG-INFO +362 -0
- subroutine-0.1.0/README.md +317 -0
- subroutine-0.1.0/alembic.ini +43 -0
- subroutine-0.1.0/docs/errors.md +132 -0
- subroutine-0.1.0/docs/hosting.md +611 -0
- subroutine-0.1.0/plugins/subroutine/.claude-plugin/plugin.json +38 -0
- subroutine-0.1.0/plugins/subroutine/.mcp.json +11 -0
- subroutine-0.1.0/plugins/subroutine/skills/subroutine/SKILL.md +186 -0
- subroutine-0.1.0/pyproject.toml +157 -0
- subroutine-0.1.0/scripts/check_licences.py +264 -0
- subroutine-0.1.0/scripts/check_release_notes.py +267 -0
- subroutine-0.1.0/src/subroutine/__init__.py +25 -0
- subroutine-0.1.0/src/subroutine/__main__.py +12 -0
- subroutine-0.1.0/src/subroutine/addressing.py +49 -0
- subroutine-0.1.0/src/subroutine/api/__init__.py +11 -0
- subroutine-0.1.0/src/subroutine/api/admin.py +117 -0
- subroutine-0.1.0/src/subroutine/api/agenda.py +115 -0
- subroutine-0.1.0/src/subroutine/api/app.py +171 -0
- subroutine-0.1.0/src/subroutine/api/comments.py +303 -0
- subroutine-0.1.0/src/subroutine/api/concurrency.py +131 -0
- subroutine-0.1.0/src/subroutine/api/dependencies.py +85 -0
- subroutine-0.1.0/src/subroutine/api/documents.py +657 -0
- subroutine-0.1.0/src/subroutine/api/events.py +204 -0
- subroutine-0.1.0/src/subroutine/api/health.py +76 -0
- subroutine-0.1.0/src/subroutine/api/identity.py +194 -0
- subroutine-0.1.0/src/subroutine/api/meta.py +797 -0
- subroutine-0.1.0/src/subroutine/api/middleware.py +86 -0
- subroutine-0.1.0/src/subroutine/api/pagination.py +304 -0
- subroutine-0.1.0/src/subroutine/api/problems.py +319 -0
- subroutine-0.1.0/src/subroutine/api/projects.py +415 -0
- subroutine-0.1.0/src/subroutine/api/query.py +156 -0
- subroutine-0.1.0/src/subroutine/api/routing.py +181 -0
- subroutine-0.1.0/src/subroutine/api/schemas.py +87 -0
- subroutine-0.1.0/src/subroutine/api/security.py +140 -0
- subroutine-0.1.0/src/subroutine/api/shaping.py +279 -0
- subroutine-0.1.0/src/subroutine/api/subjects.py +66 -0
- subroutine-0.1.0/src/subroutine/api/tasks.py +767 -0
- subroutine-0.1.0/src/subroutine/api/tokens.py +298 -0
- subroutine-0.1.0/src/subroutine/api/users.py +126 -0
- subroutine-0.1.0/src/subroutine/api/workspaces.py +398 -0
- subroutine-0.1.0/src/subroutine/auth.py +201 -0
- subroutine-0.1.0/src/subroutine/cli/__init__.py +6 -0
- subroutine-0.1.0/src/subroutine/cli/main.py +2087 -0
- subroutine-0.1.0/src/subroutine/cli/personal.py +3890 -0
- subroutine-0.1.0/src/subroutine/cli/topics.py +214 -0
- subroutine-0.1.0/src/subroutine/clients/__init__.py +6 -0
- subroutine-0.1.0/src/subroutine/clients/base.py +538 -0
- subroutine-0.1.0/src/subroutine/clients/http.py +943 -0
- subroutine-0.1.0/src/subroutine/clients/local.py +1357 -0
- subroutine-0.1.0/src/subroutine/clients/opening.py +37 -0
- subroutine-0.1.0/src/subroutine/config.py +736 -0
- subroutine-0.1.0/src/subroutine/connections.py +430 -0
- subroutine-0.1.0/src/subroutine/context.py +295 -0
- subroutine-0.1.0/src/subroutine/credentials.py +350 -0
- subroutine-0.1.0/src/subroutine/db/__init__.py +1 -0
- subroutine-0.1.0/src/subroutine/db/backup.py +1045 -0
- subroutine-0.1.0/src/subroutine/db/base.py +27 -0
- subroutine-0.1.0/src/subroutine/db/migrate.py +294 -0
- subroutine-0.1.0/src/subroutine/db/migrations/env.py +164 -0
- subroutine-0.1.0/src/subroutine/db/migrations/script.py.mako +35 -0
- subroutine-0.1.0/src/subroutine/db/migrations/versions/0c8f7a7027e6_refs_are_workspace_sequential_integers.py +215 -0
- subroutine-0.1.0/src/subroutine/db/migrations/versions/233f898a2bee_instance_timezone_workspace_timezone_.py +58 -0
- subroutine-0.1.0/src/subroutine/db/migrations/versions/2fee457e5b0b_initial_schema.py +615 -0
- subroutine-0.1.0/src/subroutine/db/migrations/versions/547fe53b263c_an_event_carries_the_subject_it_.py +195 -0
- subroutine-0.1.0/src/subroutine/db/migrations/versions/ea3e86ad12c4_workspace_slug_frees_on_soft_delete.py +45 -0
- subroutine-0.1.0/src/subroutine/db/mixins.py +148 -0
- subroutine-0.1.0/src/subroutine/db/models/__init__.py +17 -0
- subroutine-0.1.0/src/subroutine/db/models/activity.py +147 -0
- subroutine-0.1.0/src/subroutine/db/models/identity.py +262 -0
- subroutine-0.1.0/src/subroutine/db/models/project.py +165 -0
- subroutine-0.1.0/src/subroutine/db/models/system.py +53 -0
- subroutine-0.1.0/src/subroutine/db/models/vocabulary.py +172 -0
- subroutine-0.1.0/src/subroutine/db/models/work.py +477 -0
- subroutine-0.1.0/src/subroutine/db/seed.py +514 -0
- subroutine-0.1.0/src/subroutine/db/session.py +159 -0
- subroutine-0.1.0/src/subroutine/db/transfer.py +313 -0
- subroutine-0.1.0/src/subroutine/db/types.py +157 -0
- subroutine-0.1.0/src/subroutine/directory.py +170 -0
- subroutine-0.1.0/src/subroutine/domain/__init__.py +9 -0
- subroutine-0.1.0/src/subroutine/domain/agenda.py +227 -0
- subroutine-0.1.0/src/subroutine/domain/authentication.py +442 -0
- subroutine-0.1.0/src/subroutine/domain/authorization.py +599 -0
- subroutine-0.1.0/src/subroutine/domain/bootstrap.py +215 -0
- subroutine-0.1.0/src/subroutine/domain/capture.py +536 -0
- subroutine-0.1.0/src/subroutine/domain/comments.py +393 -0
- subroutine-0.1.0/src/subroutine/domain/dates.py +317 -0
- subroutine-0.1.0/src/subroutine/domain/documents.py +571 -0
- subroutine-0.1.0/src/subroutine/domain/durations.py +191 -0
- subroutine-0.1.0/src/subroutine/domain/events.py +198 -0
- subroutine-0.1.0/src/subroutine/domain/hierarchy.py +213 -0
- subroutine-0.1.0/src/subroutine/domain/instances.py +55 -0
- subroutine-0.1.0/src/subroutine/domain/links.py +552 -0
- subroutine-0.1.0/src/subroutine/domain/local.py +291 -0
- subroutine-0.1.0/src/subroutine/domain/mentions.py +211 -0
- subroutine-0.1.0/src/subroutine/domain/ordering.py +326 -0
- subroutine-0.1.0/src/subroutine/domain/paging.py +52 -0
- subroutine-0.1.0/src/subroutine/domain/patch.py +43 -0
- subroutine-0.1.0/src/subroutine/domain/projects.py +629 -0
- subroutine-0.1.0/src/subroutine/domain/readiness.py +153 -0
- subroutine-0.1.0/src/subroutine/domain/refs.py +222 -0
- subroutine-0.1.0/src/subroutine/domain/schedule.py +391 -0
- subroutine-0.1.0/src/subroutine/domain/scoping.py +194 -0
- subroutine-0.1.0/src/subroutine/domain/search.py +60 -0
- subroutine-0.1.0/src/subroutine/domain/selection.py +198 -0
- subroutine-0.1.0/src/subroutine/domain/tags.py +273 -0
- subroutine-0.1.0/src/subroutine/domain/tasks.py +1169 -0
- subroutine-0.1.0/src/subroutine/domain/text.py +104 -0
- subroutine-0.1.0/src/subroutine/domain/tokens.py +138 -0
- subroutine-0.1.0/src/subroutine/domain/users.py +270 -0
- subroutine-0.1.0/src/subroutine/domain/versions.py +79 -0
- subroutine-0.1.0/src/subroutine/domain/workspaces.py +571 -0
- subroutine-0.1.0/src/subroutine/errors.py +590 -0
- subroutine-0.1.0/src/subroutine/fanout.py +198 -0
- subroutine-0.1.0/src/subroutine/mcp/__init__.py +15 -0
- subroutine-0.1.0/src/subroutine/mcp/protocol.py +283 -0
- subroutine-0.1.0/src/subroutine/mcp/session.py +78 -0
- subroutine-0.1.0/src/subroutine/mcp/tools.py +780 -0
- subroutine-0.1.0/src/subroutine/permissions.py +132 -0
- subroutine-0.1.0/src/subroutine/py.typed +0 -0
- subroutine-0.1.0/src/subroutine/views.py +1226 -0
- subroutine-0.1.0/tests/api_support.py +148 -0
- subroutine-0.1.0/tests/conftest.py +280 -0
- subroutine-0.1.0/tests/sample_models.py +64 -0
- subroutine-0.1.0/tests/test_actor_discipline.py +241 -0
- subroutine-0.1.0/tests/test_agenda.py +457 -0
- subroutine-0.1.0/tests/test_api_app.py +277 -0
- subroutine-0.1.0/tests/test_api_authentication.py +421 -0
- subroutine-0.1.0/tests/test_api_comments.py +233 -0
- subroutine-0.1.0/tests/test_api_concurrency.py +225 -0
- subroutine-0.1.0/tests/test_api_documents.py +374 -0
- subroutine-0.1.0/tests/test_api_events.py +336 -0
- subroutine-0.1.0/tests/test_api_examples.py +204 -0
- subroutine-0.1.0/tests/test_api_meta.py +375 -0
- subroutine-0.1.0/tests/test_api_problems.py +185 -0
- subroutine-0.1.0/tests/test_api_projects.py +338 -0
- subroutine-0.1.0/tests/test_api_routing.py +353 -0
- subroutine-0.1.0/tests/test_api_shaping.py +445 -0
- subroutine-0.1.0/tests/test_api_tasks.py +2103 -0
- subroutine-0.1.0/tests/test_api_tokens.py +261 -0
- subroutine-0.1.0/tests/test_api_workspaces.py +374 -0
- subroutine-0.1.0/tests/test_api_writability.py +499 -0
- subroutine-0.1.0/tests/test_auth.py +160 -0
- subroutine-0.1.0/tests/test_authentication.py +353 -0
- subroutine-0.1.0/tests/test_authorization.py +767 -0
- subroutine-0.1.0/tests/test_bootstrap.py +372 -0
- subroutine-0.1.0/tests/test_capture.py +616 -0
- subroutine-0.1.0/tests/test_cli_connections.py +965 -0
- subroutine-0.1.0/tests/test_cli_help.py +123 -0
- subroutine-0.1.0/tests/test_config.py +298 -0
- subroutine-0.1.0/tests/test_connections.py +486 -0
- subroutine-0.1.0/tests/test_context.py +229 -0
- subroutine-0.1.0/tests/test_dates.py +277 -0
- subroutine-0.1.0/tests/test_db_types.py +172 -0
- subroutine-0.1.0/tests/test_directory.py +190 -0
- subroutine-0.1.0/tests/test_documentation.py +91 -0
- subroutine-0.1.0/tests/test_durations.py +182 -0
- subroutine-0.1.0/tests/test_errors.py +204 -0
- subroutine-0.1.0/tests/test_fanout.py +230 -0
- subroutine-0.1.0/tests/test_fixtures.py +50 -0
- subroutine-0.1.0/tests/test_imports.py +428 -0
- subroutine-0.1.0/tests/test_instances.py +1165 -0
- subroutine-0.1.0/tests/test_isolation.py +251 -0
- subroutine-0.1.0/tests/test_local_mode.py +511 -0
- subroutine-0.1.0/tests/test_mcp.py +1039 -0
- subroutine-0.1.0/tests/test_migrations.py +713 -0
- subroutine-0.1.0/tests/test_models.py +510 -0
- subroutine-0.1.0/tests/test_multi_user.py +222 -0
- subroutine-0.1.0/tests/test_ordering.py +154 -0
- subroutine-0.1.0/tests/test_personal_path.py +2978 -0
- subroutine-0.1.0/tests/test_plugin.py +259 -0
- subroutine-0.1.0/tests/test_reach.py +563 -0
- subroutine-0.1.0/tests/test_release_notes.py +303 -0
- subroutine-0.1.0/tests/test_schedule.py +472 -0
- subroutine-0.1.0/tests/test_scoping.py +372 -0
- subroutine-0.1.0/tests/test_seed.py +436 -0
- subroutine-0.1.0/tests/test_services.py +1325 -0
- subroutine-0.1.0/tests/test_smoke.py +59 -0
- subroutine-0.1.0/tests/test_spec_endpoints.py +160 -0
- subroutine-0.1.0/tests/test_sqlite_concurrency.py +187 -0
- subroutine-0.1.0/tests/test_transfer.py +254 -0
- subroutine-0.1.0/tests/test_transport_equivalence.py +1373 -0
- subroutine-0.1.0/tests/test_upgrade.py +256 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
3
|
+
"name": "subroutine",
|
|
4
|
+
"description": "Project management for people and agents, in equal measure.",
|
|
5
|
+
"owner": {
|
|
6
|
+
"name": "Simon Holliday",
|
|
7
|
+
"email": "simon.holliday@protonmail.com"
|
|
8
|
+
},
|
|
9
|
+
"plugins": [
|
|
10
|
+
{
|
|
11
|
+
"name": "subroutine",
|
|
12
|
+
"source": "./plugins/subroutine",
|
|
13
|
+
"description": "Give Claude a shared, durable task list: file work, find what is ready, record what happened, and leave the next session something to read. Needs Subroutine itself installed separately — 'pip install subroutine', then 'subroutine init'.",
|
|
14
|
+
"author": {
|
|
15
|
+
"name": "Simon Holliday",
|
|
16
|
+
"email": "simon.holliday@protonmail.com"
|
|
17
|
+
},
|
|
18
|
+
"category": "productivity",
|
|
19
|
+
"keywords": [
|
|
20
|
+
"tasks",
|
|
21
|
+
"project-management",
|
|
22
|
+
"todo",
|
|
23
|
+
"self-hosted",
|
|
24
|
+
"planning"
|
|
25
|
+
],
|
|
26
|
+
"homepage": "https://github.com/simonholliday/subroutine"
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
# Two jobs rather than one matrix doing everything. Lint and type results are identical on
|
|
4
|
+
# every Python version, so running them three times would add nothing but three places to
|
|
5
|
+
# read the same failure.
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
branches: [main]
|
|
10
|
+
pull_request:
|
|
11
|
+
workflow_dispatch:
|
|
12
|
+
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: ci-${{ github.ref }}
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
env:
|
|
21
|
+
PIP_DISABLE_PIP_VERSION_CHECK: "1"
|
|
22
|
+
|
|
23
|
+
jobs:
|
|
24
|
+
checks:
|
|
25
|
+
name: Lint and types
|
|
26
|
+
runs-on: ubuntu-latest
|
|
27
|
+
|
|
28
|
+
steps:
|
|
29
|
+
# Full history and tags, because the release check compares this commit's migration
|
|
30
|
+
# head against the one at the most recent tag. A shallow clone has neither, and the
|
|
31
|
+
# check would find no previous tag and cheerfully pass every time.
|
|
32
|
+
- uses: actions/checkout@v4
|
|
33
|
+
with:
|
|
34
|
+
fetch-depth: 0
|
|
35
|
+
|
|
36
|
+
- uses: actions/setup-python@v5
|
|
37
|
+
with:
|
|
38
|
+
python-version: "3.12"
|
|
39
|
+
cache: pip
|
|
40
|
+
cache-dependency-path: pyproject.toml
|
|
41
|
+
|
|
42
|
+
- name: Install
|
|
43
|
+
run: python -m pip install -e '.[dev,postgres]'
|
|
44
|
+
|
|
45
|
+
# `ruff check` only. `ruff format` would strip the space in `def foo (x)` and
|
|
46
|
+
# re-indent the whole codebase with spaces — it is not a formatter on this project.
|
|
47
|
+
- name: Ruff
|
|
48
|
+
run: ruff check .
|
|
49
|
+
|
|
50
|
+
- name: Mypy
|
|
51
|
+
run: mypy src tests scripts
|
|
52
|
+
|
|
53
|
+
# A release that moves the schema has to say so in CHANGELOG.md, so that somebody can
|
|
54
|
+
# *plan* the database upgrade rather than meet one halfway through installing
|
|
55
|
+
# something (SPEC.md §12.4a). Derived from the migration directory rather than from
|
|
56
|
+
# anybody remembering at release time, which is when nobody does.
|
|
57
|
+
- name: Release notes
|
|
58
|
+
run: python scripts/check_release_notes.py
|
|
59
|
+
|
|
60
|
+
# Subroutine is AGPL and is also offered commercially, and both rest on being able to
|
|
61
|
+
# grant rights in the whole of what ships. A copyleft dependency takes that away, and it
|
|
62
|
+
# will arrive inside somebody else's requirements rather than as a decision anybody
|
|
63
|
+
# makes. Separate job with a clean install: development tools are not distributed, so a
|
|
64
|
+
# GPL linter constrains nothing and should not fail this.
|
|
65
|
+
licences:
|
|
66
|
+
name: Dependency licences
|
|
67
|
+
runs-on: ubuntu-latest
|
|
68
|
+
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/checkout@v4
|
|
71
|
+
|
|
72
|
+
- uses: actions/setup-python@v5
|
|
73
|
+
with:
|
|
74
|
+
python-version: "3.12"
|
|
75
|
+
cache: pip
|
|
76
|
+
cache-dependency-path: pyproject.toml
|
|
77
|
+
|
|
78
|
+
# `packaging` is the script's own tool for walking the closure, not a dependency of
|
|
79
|
+
# Subroutine — it is installed here rather than declared, so it never ships.
|
|
80
|
+
- name: Install runtime dependencies only
|
|
81
|
+
run: python -m pip install '.[postgres]' packaging
|
|
82
|
+
|
|
83
|
+
- name: Check
|
|
84
|
+
run: python scripts/check_licences.py
|
|
85
|
+
|
|
86
|
+
# Checked by the tool that will read them (SPEC.md §21). `tests/test_plugin.py` covers what
|
|
87
|
+
# the manifests must *say* — that the version matches the package, that every declared option
|
|
88
|
+
# is substituted somewhere, that the token is held as a secret. Only Claude Code itself can
|
|
89
|
+
# say whether they conform to its schema, and a manifest that does not is one nobody can
|
|
90
|
+
# install at all.
|
|
91
|
+
#
|
|
92
|
+
# Its own job rather than a step in `checks`, because it needs a Node package the rest of the
|
|
93
|
+
# build has no use for. Jobs run in parallel, so this costs nothing on the clock and does not
|
|
94
|
+
# slow the lint feedback everybody actually waits for.
|
|
95
|
+
plugin:
|
|
96
|
+
name: Plugin manifests
|
|
97
|
+
runs-on: ubuntu-latest
|
|
98
|
+
|
|
99
|
+
steps:
|
|
100
|
+
- uses: actions/checkout@v4
|
|
101
|
+
|
|
102
|
+
- uses: actions/setup-node@v4
|
|
103
|
+
with:
|
|
104
|
+
node-version: "22"
|
|
105
|
+
|
|
106
|
+
- name: Install Claude Code
|
|
107
|
+
run: npm install -g @anthropic-ai/claude-code
|
|
108
|
+
|
|
109
|
+
- name: Validate
|
|
110
|
+
run: |
|
|
111
|
+
claude plugin validate .
|
|
112
|
+
claude plugin validate ./plugins/subroutine
|
|
113
|
+
|
|
114
|
+
test:
|
|
115
|
+
name: Tests (Python ${{ matrix.python-version }})
|
|
116
|
+
runs-on: ubuntu-latest
|
|
117
|
+
|
|
118
|
+
strategy:
|
|
119
|
+
fail-fast: false
|
|
120
|
+
matrix:
|
|
121
|
+
python-version: ["3.11", "3.12", "3.13"]
|
|
122
|
+
|
|
123
|
+
services:
|
|
124
|
+
postgres:
|
|
125
|
+
image: postgres:16
|
|
126
|
+
env:
|
|
127
|
+
POSTGRES_USER: postgres
|
|
128
|
+
POSTGRES_PASSWORD: postgres
|
|
129
|
+
POSTGRES_DB: postgres
|
|
130
|
+
ports:
|
|
131
|
+
- 5432:5432
|
|
132
|
+
# Without this the suite can start before the server accepts connections, and the
|
|
133
|
+
# PostgreSQL half would skip rather than fail — see SUBROUTINE_TEST_REQUIRE_POSTGRES.
|
|
134
|
+
options: >-
|
|
135
|
+
--health-cmd "pg_isready -U postgres"
|
|
136
|
+
--health-interval 5s
|
|
137
|
+
--health-timeout 5s
|
|
138
|
+
--health-retries 10
|
|
139
|
+
|
|
140
|
+
steps:
|
|
141
|
+
- uses: actions/checkout@v4
|
|
142
|
+
|
|
143
|
+
- uses: actions/setup-python@v5
|
|
144
|
+
with:
|
|
145
|
+
python-version: ${{ matrix.python-version }}
|
|
146
|
+
cache: pip
|
|
147
|
+
cache-dependency-path: pyproject.toml
|
|
148
|
+
|
|
149
|
+
- name: Install
|
|
150
|
+
run: python -m pip install -e '.[dev,postgres]'
|
|
151
|
+
|
|
152
|
+
# The whole suite runs twice, once per backend. The drift check that compares the
|
|
153
|
+
# models against the migration head is one of those tests, and it is the reason this
|
|
154
|
+
# job matters more than it looks: portability breaks are caught on the day they are
|
|
155
|
+
# introduced rather than at the first PostgreSQL deployment.
|
|
156
|
+
- name: Tests on SQLite and PostgreSQL
|
|
157
|
+
env:
|
|
158
|
+
SUBROUTINE_TEST_POSTGRES_ADMIN_URL: postgresql+psycopg://postgres:postgres@localhost:5432/postgres
|
|
159
|
+
SUBROUTINE_TEST_REQUIRE_POSTGRES: "1"
|
|
160
|
+
run: pytest -q
|
|
161
|
+
|
|
162
|
+
# Proves the thing a new user actually does, on a clean machine with nothing configured:
|
|
163
|
+
# install the package, run one command, get a working database. It is separate from the
|
|
164
|
+
# test job because it must not see the repository's test fixtures or a pre-made database.
|
|
165
|
+
first-run:
|
|
166
|
+
name: First run
|
|
167
|
+
runs-on: ubuntu-latest
|
|
168
|
+
|
|
169
|
+
steps:
|
|
170
|
+
- uses: actions/checkout@v4
|
|
171
|
+
|
|
172
|
+
- uses: actions/setup-python@v5
|
|
173
|
+
with:
|
|
174
|
+
python-version: "3.12"
|
|
175
|
+
cache: pip
|
|
176
|
+
cache-dependency-path: pyproject.toml
|
|
177
|
+
|
|
178
|
+
- name: Install as a user would
|
|
179
|
+
run: python -m pip install .
|
|
180
|
+
|
|
181
|
+
- name: subroutine init
|
|
182
|
+
run: |
|
|
183
|
+
set -euo pipefail
|
|
184
|
+
output="$(subroutine init 2>&1)"
|
|
185
|
+
echo "$output"
|
|
186
|
+
expected='Ready. Try: subroutine add "something to do"'
|
|
187
|
+
if [ "$output" != "$expected" ]; then
|
|
188
|
+
echo "init printed something other than its one line" >&2
|
|
189
|
+
exit 1
|
|
190
|
+
fi
|
|
191
|
+
|
|
192
|
+
- name: The database is queryable afterwards
|
|
193
|
+
run: |
|
|
194
|
+
set -euo pipefail
|
|
195
|
+
subroutine db current | grep -q '^Schema is at '
|
|
196
|
+
subroutine init | grep -q 'Already set up'
|
|
197
|
+
|
|
198
|
+
# SPEC.md §13.5b, the gating criterion for M2: a fresh installation to a working
|
|
199
|
+
# to-do list in three commands and a completed task in a fourth, with none of the
|
|
200
|
+
# output mentioning a workspace, a status, a project, a criterion, a verification, a
|
|
201
|
+
# session or a claim. The suite runs this too; it runs here as well because this job
|
|
202
|
+
# is the only one with nothing configured and no fixtures — which is the state the
|
|
203
|
+
# person it is written about is actually in.
|
|
204
|
+
- name: The personal test
|
|
205
|
+
run: |
|
|
206
|
+
set -euo pipefail
|
|
207
|
+
# `2>&1` on every one of them, because a warning is as visible to the person as
|
|
208
|
+
# an answer is. Capturing stdout alone is what let a checkout marker greet a
|
|
209
|
+
# stranger with the words *workspace* and *project* while this job reported
|
|
210
|
+
# success (`SR#219`, `SR#222`).
|
|
211
|
+
subroutine add "Call the dentist before Sunday" 2>&1 | tee /tmp/add.txt
|
|
212
|
+
subroutine today 2>&1 | tee /tmp/today.txt
|
|
213
|
+
subroutine done 1 2>&1 | tee /tmp/done.txt
|
|
214
|
+
grep -q 'Added: Call the dentist' /tmp/add.txt
|
|
215
|
+
grep -q 'Call the dentist' /tmp/today.txt
|
|
216
|
+
grep -q 'Done: Call the dentist' /tmp/done.txt
|
|
217
|
+
if grep -Eiq 'workspace|status|project|criterion|verification|session|claim' \
|
|
218
|
+
/tmp/add.txt /tmp/today.txt /tmp/done.txt; then
|
|
219
|
+
echo "The personal path mentioned something §1.4 says it must not." >&2
|
|
220
|
+
exit 1
|
|
221
|
+
fi
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishing is the one act here that cannot be undone: PyPI refuses a version it has already
|
|
4
|
+
# seen, even after a deletion, so a broken 0.1.0 is not fixed but abandoned. Everything in this
|
|
5
|
+
# file exists to spend that single attempt well — the suite runs, the metadata is checked, and
|
|
6
|
+
# the tag is made to agree with the version.
|
|
7
|
+
#
|
|
8
|
+
# **A failed upload spends nothing; only a successful upload of a bad artefact does.** That is
|
|
9
|
+
# why TestPyPI is a rehearsal somebody asks for rather than a gate on the way through. What it
|
|
10
|
+
# proves is the OIDC handshake, and if that is misconfigured the real upload simply fails and is
|
|
11
|
+
# retried; what it does not prove is the artefact, which `twine check` and a clean install from
|
|
12
|
+
# the built wheel already cover. Registering a publisher there is a separate account and a
|
|
13
|
+
# separate act, so a release must not wait on one.
|
|
14
|
+
#
|
|
15
|
+
# Hence: a **tag** publishes to PyPI, a **manual** run rehearses on TestPyPI, and neither can do
|
|
16
|
+
# the other's job by accident.
|
|
17
|
+
#
|
|
18
|
+
# No stored credential anywhere. Both uploads authenticate by OIDC against a trusted publisher
|
|
19
|
+
# (SPEC.md §2.2), which is why each publishing job asks for `id-token: write` and nothing else
|
|
20
|
+
# does.
|
|
21
|
+
|
|
22
|
+
on:
|
|
23
|
+
push:
|
|
24
|
+
tags: ["v*"]
|
|
25
|
+
workflow_dispatch:
|
|
26
|
+
|
|
27
|
+
permissions:
|
|
28
|
+
contents: read
|
|
29
|
+
|
|
30
|
+
jobs:
|
|
31
|
+
# CI runs on pushes to main, and a tag is not one — so without this a release could publish
|
|
32
|
+
# code no suite ever saw. One version rather than the full matrix: this is a gate on the
|
|
33
|
+
# artefact, not the compatibility run that already happened on the commit.
|
|
34
|
+
test:
|
|
35
|
+
name: Tests
|
|
36
|
+
runs-on: ubuntu-latest
|
|
37
|
+
|
|
38
|
+
services:
|
|
39
|
+
postgres:
|
|
40
|
+
image: postgres:16
|
|
41
|
+
env:
|
|
42
|
+
POSTGRES_USER: postgres
|
|
43
|
+
POSTGRES_PASSWORD: postgres
|
|
44
|
+
POSTGRES_DB: postgres
|
|
45
|
+
ports:
|
|
46
|
+
- 5432:5432
|
|
47
|
+
options: >-
|
|
48
|
+
--health-cmd "pg_isready -U postgres"
|
|
49
|
+
--health-interval 5s
|
|
50
|
+
--health-timeout 5s
|
|
51
|
+
--health-retries 10
|
|
52
|
+
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@v4
|
|
55
|
+
|
|
56
|
+
- uses: actions/setup-python@v5
|
|
57
|
+
with:
|
|
58
|
+
python-version: "3.12"
|
|
59
|
+
cache: pip
|
|
60
|
+
cache-dependency-path: pyproject.toml
|
|
61
|
+
|
|
62
|
+
- name: Install
|
|
63
|
+
run: python -m pip install -e '.[dev,postgres]'
|
|
64
|
+
|
|
65
|
+
# Both backends, and an unreachable PostgreSQL is a failure rather than a skip — a
|
|
66
|
+
# release verified on half a test run is not verified.
|
|
67
|
+
- name: Tests on SQLite and PostgreSQL
|
|
68
|
+
env:
|
|
69
|
+
SUBROUTINE_TEST_POSTGRES_ADMIN_URL: postgresql+psycopg://postgres:postgres@localhost:5432/postgres
|
|
70
|
+
SUBROUTINE_TEST_REQUIRE_POSTGRES: "1"
|
|
71
|
+
run: pytest
|
|
72
|
+
|
|
73
|
+
build:
|
|
74
|
+
name: Build
|
|
75
|
+
needs: test
|
|
76
|
+
runs-on: ubuntu-latest
|
|
77
|
+
|
|
78
|
+
steps:
|
|
79
|
+
# Full history and tags: `check_release_notes.py` compares this commit's migration head
|
|
80
|
+
# against the head at the previous tag, and a shallow clone has neither.
|
|
81
|
+
- uses: actions/checkout@v4
|
|
82
|
+
with:
|
|
83
|
+
fetch-depth: 0
|
|
84
|
+
|
|
85
|
+
- uses: actions/setup-python@v5
|
|
86
|
+
with:
|
|
87
|
+
python-version: "3.12"
|
|
88
|
+
|
|
89
|
+
# Subroutine itself as well as the build tools, because `check_release_notes.py` reads
|
|
90
|
+
# the migration head through `subroutine.db.migrate` rather than by parsing files — the
|
|
91
|
+
# asymmetry that script's own docstring argues for. Installing only `build` and `twine`
|
|
92
|
+
# left it with `ModuleNotFoundError: No module named 'subroutine'`, which passed every
|
|
93
|
+
# local check because a shell that can run the script already has the package (`SR#230`).
|
|
94
|
+
- name: Install the build tools, and Subroutine itself
|
|
95
|
+
run: python -m pip install -e . build twine
|
|
96
|
+
|
|
97
|
+
- name: Build the wheel and the sdist
|
|
98
|
+
run: python -m build
|
|
99
|
+
|
|
100
|
+
- name: Check the metadata
|
|
101
|
+
run: twine check dist/*
|
|
102
|
+
|
|
103
|
+
# The tag says which release this is and `pyproject.toml` says what will be uploaded
|
|
104
|
+
# under that name. If they disagree, the thing published is not the thing the tag names
|
|
105
|
+
# — and this is the last moment that mistake costs nothing.
|
|
106
|
+
- name: The tag names the version being built
|
|
107
|
+
if: startsWith(github.ref, 'refs/tags/')
|
|
108
|
+
run: |
|
|
109
|
+
set -euo pipefail
|
|
110
|
+
tagged="${GITHUB_REF_NAME#v}"
|
|
111
|
+
built="$(python -c 'import pathlib, tomllib; print(tomllib.loads(pathlib.Path("pyproject.toml").read_text())["project"]["version"])')"
|
|
112
|
+
if [ "$tagged" != "$built" ]; then
|
|
113
|
+
echo "the tag $GITHUB_REF_NAME does not name version $built" >&2
|
|
114
|
+
exit 1
|
|
115
|
+
fi
|
|
116
|
+
echo "tag and version agree on $built"
|
|
117
|
+
|
|
118
|
+
# A release that moves the schema has to say so in the changelog (SPEC.md §12.4a). CI
|
|
119
|
+
# checks this on every commit; it is checked again here because this is the run where
|
|
120
|
+
# being wrong reaches somebody else's database.
|
|
121
|
+
- name: Release notes
|
|
122
|
+
run: python scripts/check_release_notes.py
|
|
123
|
+
|
|
124
|
+
- uses: actions/upload-artifact@v4
|
|
125
|
+
with:
|
|
126
|
+
name: dist
|
|
127
|
+
path: dist/
|
|
128
|
+
|
|
129
|
+
# The rehearsal: same artefacts, same mechanism, a registry where a mistake costs nothing —
|
|
130
|
+
# run when somebody asks for it. Skipped entirely on a tag, so a release is never held up for
|
|
131
|
+
# the want of a publisher registered on a different site.
|
|
132
|
+
testpypi:
|
|
133
|
+
name: TestPyPI
|
|
134
|
+
needs: build
|
|
135
|
+
if: github.event_name == 'workflow_dispatch'
|
|
136
|
+
runs-on: ubuntu-latest
|
|
137
|
+
|
|
138
|
+
environment:
|
|
139
|
+
name: testpypi
|
|
140
|
+
url: https://test.pypi.org/p/subroutine
|
|
141
|
+
|
|
142
|
+
permissions:
|
|
143
|
+
id-token: write
|
|
144
|
+
|
|
145
|
+
steps:
|
|
146
|
+
- uses: actions/download-artifact@v4
|
|
147
|
+
with:
|
|
148
|
+
name: dist
|
|
149
|
+
path: dist/
|
|
150
|
+
|
|
151
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
152
|
+
with:
|
|
153
|
+
repository-url: https://test.pypi.org/legacy/
|
|
154
|
+
|
|
155
|
+
# `push` can only mean a tag here, since that is this workflow's only push trigger — so the
|
|
156
|
+
# condition says "a release, not a rehearsal" rather than restating the trigger. Guarded by
|
|
157
|
+
# the `pypi` environment as well, so a required reviewer on it makes the upload a decision
|
|
158
|
+
# somebody takes rather than a consequence of pushing a tag.
|
|
159
|
+
pypi:
|
|
160
|
+
name: PyPI
|
|
161
|
+
needs: build
|
|
162
|
+
if: github.event_name == 'push'
|
|
163
|
+
runs-on: ubuntu-latest
|
|
164
|
+
|
|
165
|
+
environment:
|
|
166
|
+
name: pypi
|
|
167
|
+
url: https://pypi.org/p/subroutine
|
|
168
|
+
|
|
169
|
+
permissions:
|
|
170
|
+
id-token: write
|
|
171
|
+
|
|
172
|
+
steps:
|
|
173
|
+
- uses: actions/download-artifact@v4
|
|
174
|
+
with:
|
|
175
|
+
name: dist
|
|
176
|
+
path: dist/
|
|
177
|
+
|
|
178
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.so
|
|
5
|
+
.Python
|
|
6
|
+
build/
|
|
7
|
+
dist/
|
|
8
|
+
*.egg-info/
|
|
9
|
+
.eggs/
|
|
10
|
+
|
|
11
|
+
# Virtualenvs — the house convention keeps these outside the tree, but guard anyway
|
|
12
|
+
.venv/
|
|
13
|
+
venv/
|
|
14
|
+
env/
|
|
15
|
+
|
|
16
|
+
# Tooling caches
|
|
17
|
+
.pytest_cache/
|
|
18
|
+
.mypy_cache/
|
|
19
|
+
.ruff_cache/
|
|
20
|
+
.coverage
|
|
21
|
+
.coverage.*
|
|
22
|
+
htmlcov/
|
|
23
|
+
coverage.xml
|
|
24
|
+
|
|
25
|
+
# Local data — a stray `subroutine init` in the working tree must never be committed
|
|
26
|
+
*.db
|
|
27
|
+
*.db-wal
|
|
28
|
+
*.db-shm
|
|
29
|
+
*.sqlite3
|
|
30
|
+
/data/
|
|
31
|
+
/instance/
|
|
32
|
+
|
|
33
|
+
# Secrets and local configuration
|
|
34
|
+
.env
|
|
35
|
+
.env.*
|
|
36
|
+
config.local.toml
|
|
37
|
+
|
|
38
|
+
# Editors
|
|
39
|
+
.idea/
|
|
40
|
+
*.swp
|
|
41
|
+
.DS_Store
|
|
42
|
+
|
|
43
|
+
# Local documentation
|
|
44
|
+
CLAUDE.md
|
|
45
|
+
COMPETITIVE-ANALYSIS.md
|
|
46
|
+
SPEC.md
|
|
47
|
+
MVP-PLAN.md
|
|
48
|
+
reviews
|
|
49
|
+
|
|
50
|
+
# Committing this is right for a team sharing one instance, and wrong here: a clone of
|
|
51
|
+
# *Subroutine itself* goes to strangers who share nothing, and the marker would name a
|
|
52
|
+
# workspace and a project that exist on no machine but ours (`SR#219`).
|
|
53
|
+
.subroutine
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
Notable changes, newest first. Dates are the date of the release.
|
|
4
|
+
|
|
5
|
+
**A release that changes the database schema says so at the top of its own section**, and CI
|
|
6
|
+
fails if it does not. `scripts/check_release_notes.py` compares the migration head against the
|
|
7
|
+
most recent tag rather than trusting anybody to remember, so the notice appears in the
|
|
8
|
+
unreleased section on the day the migration lands — written by whoever wrote the migration, not
|
|
9
|
+
by whoever cuts the release in a hurry three weeks later. `--emit` prints the wording.
|
|
10
|
+
|
|
11
|
+
The point of it is that you can *plan* a database upgrade instead of meeting one halfway
|
|
12
|
+
through installing something. See [docs/hosting.md](docs/hosting.md#upgrading) for what the
|
|
13
|
+
upgrade involves.
|
|
14
|
+
|
|
15
|
+
## 0.1.0 — 2026-08-01
|
|
16
|
+
|
|
17
|
+
The first public release. Everything here is new, so this section says what exists rather than
|
|
18
|
+
what changed.
|
|
19
|
+
|
|
20
|
+
No migration notice: there is no previous release to upgrade from, and a fresh install builds
|
|
21
|
+
its schema outright with `subroutine init`.
|
|
22
|
+
|
|
23
|
+
### A personal to-do list
|
|
24
|
+
|
|
25
|
+
- `subroutine init`, `add`, `today`, `done`, `plan`, `defer`, `list`, `search`, `show`,
|
|
26
|
+
`comment`, `project create`, `project list` and `doc create`. Three commands from install to
|
|
27
|
+
a working list, and a fourth to tick something off.
|
|
28
|
+
- Quick capture: `subroutine add "Fix the deploy script by friday !4/2 ~2h #ops"` sets the
|
|
29
|
+
deadline, both priority axes, the estimate and a tag from the line you already type.
|
|
30
|
+
- Items are addressed by a number allocated once and never reused, so it goes on meaning that
|
|
31
|
+
item after you have finished a dozen others.
|
|
32
|
+
- `subroutine start` and `subroutine stop`, so the list can say what you are in the middle of.
|
|
33
|
+
- `subroutine link 42 blocks 43` and `subroutine unlink`. `--ready` reads those links, so
|
|
34
|
+
this is how that filter learns anything.
|
|
35
|
+
- `subroutine delete` and `subroutine restore`, with `list --trash` to see what is in there.
|
|
36
|
+
Deleting is soft, so the wrong number costs nothing.
|
|
37
|
+
- `subroutine list --ready` shows only work that can actually be started — nothing unfinished
|
|
38
|
+
blocks it and it is not deferred. It is the question a backlog cannot answer.
|
|
39
|
+
- `subroutine help` and `subroutine --help` do the same thing: they list the commands.
|
|
40
|
+
`subroutine explain` covers the ideas behind them — dates, refs, the capture shorthand.
|
|
41
|
+
|
|
42
|
+
### An HTTP API, and agents as first-class users
|
|
43
|
+
|
|
44
|
+
- The HTTP API under `/v1`, with the same data model and the same permission checks the
|
|
45
|
+
CLI uses. `GET /v1/docs/agent` is the guide an agent should read first.
|
|
46
|
+
- Scoped bearer tokens: an agent's credential can be narrower than the person who issued it,
|
|
47
|
+
and may never be wider. Per-workspace pins and per-permission scopes.
|
|
48
|
+
- `subroutine token create --username ana` issues for a person, `--service-account claude` for
|
|
49
|
+
a machine identity, creating it as it goes. Two flags because they are two decisions, and
|
|
50
|
+
neither will issue a credential for an account that could not use it.
|
|
51
|
+
- `subroutine mcp` serves the same instance over the Model Context Protocol, in nine tools —
|
|
52
|
+
including `link` and `project`, so an agent can say what blocks what and file its own work.
|
|
53
|
+
- **A Claude Code plugin**, which wires those tools up and carries a skill describing the
|
|
54
|
+
practice — including how to adopt Subroutine in a project that does not use it yet.
|
|
55
|
+
- Attribution on everything, a comment thread per item, and a history of every change.
|
|
56
|
+
|
|
57
|
+
### Running it
|
|
58
|
+
|
|
59
|
+
- `subroutine serve` listens on loopback and refuses a wider bind without TLS in front of it.
|
|
60
|
+
- SQLite by default with no configuration; PostgreSQL with the `postgres` extra.
|
|
61
|
+
- `subroutine upgrade` — reports both schema versions, takes a verified backup, migrates, then
|
|
62
|
+
reads the result back. It does not install software, deliberately.
|
|
63
|
+
- Backups to a directory of your choosing, verified where they land, and a restore that makes
|
|
64
|
+
you say whether it is a recovery or a clone.
|
|
65
|
+
- Separate instances on one machine with `--profile`, isolated across all three XDG roots.
|
|
66
|
+
|
|
67
|
+
### Known limits
|
|
68
|
+
|
|
69
|
+
Session handoffs, verification evidence and claims are specified and not built — as are
|
|
70
|
+
attachments, calendar feeds, recurring tasks, a `GET /v1/changes` feed, and manual reordering.
|
|
71
|
+
There is no web UI.
|
|
72
|
+
|
|
73
|
+
Two of those are *refused out loud* rather than merely absent, which is worth telling apart:
|
|
74
|
+
the capture grammar recognises `every monday` well enough to leave it in your title and say it
|
|
75
|
+
did nothing with it, and a calendar credential presented to the API is turned down by name.
|
|
76
|
+
The rest are simply not there yet.
|
subroutine-0.1.0/CLA.md
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Subroutine Contributor Licence Agreement
|
|
2
|
+
|
|
3
|
+
Adapted from the Apache Software Foundation Individual Contributor Licence Agreement
|
|
4
|
+
v2.0. Version 1.0, dated 2026-07-29.
|
|
5
|
+
|
|
6
|
+
You agree to this by saying so in your pull request:
|
|
7
|
+
|
|
8
|
+
> I have read the CLA document and I hereby agree to its terms.
|
|
9
|
+
|
|
10
|
+
## Why this exists
|
|
11
|
+
|
|
12
|
+
Subroutine is published under AGPL-3.0-or-later and is also offered under a commercial
|
|
13
|
+
licence by separate agreement. Offering both requires the ability to grant rights in the
|
|
14
|
+
whole of the code, including your contribution. This document is how you grant those
|
|
15
|
+
rights.
|
|
16
|
+
|
|
17
|
+
**You keep your copyright.** This is a licence, not an assignment. Your contribution
|
|
18
|
+
remains yours and you may use it however you wish, elsewhere and for any purpose.
|
|
19
|
+
|
|
20
|
+
## Agreement
|
|
21
|
+
|
|
22
|
+
By contributing to Subroutine you accept the following terms for your present and future
|
|
23
|
+
contributions. If you do not agree, please do not contribute.
|
|
24
|
+
|
|
25
|
+
**1. Definitions.** "You" means the copyright owner, or the person legally authorised by
|
|
26
|
+
the copyright owner, entering into this agreement. "The Project Owner" means Simon
|
|
27
|
+
Holliday. "Contribution" means any original work of authorship, including any changes or
|
|
28
|
+
additions to existing work, that You intentionally submit to the Project Owner for
|
|
29
|
+
inclusion in Subroutine. "Submit" means any form of communication sent to the Project
|
|
30
|
+
Owner or its representatives, including but not limited to source control systems, issue
|
|
31
|
+
trackers and mailing lists, but excluding communication conspicuously marked "Not a
|
|
32
|
+
Contribution".
|
|
33
|
+
|
|
34
|
+
**2. Grant of copyright licence.** You grant to the Project Owner, and to recipients of
|
|
35
|
+
software distributed by the Project Owner, a perpetual, worldwide, non-exclusive,
|
|
36
|
+
royalty-free, irrevocable copyright licence to reproduce, prepare derivative works of,
|
|
37
|
+
publicly display, publicly perform, sublicense and distribute Your Contributions and
|
|
38
|
+
such derivative works.
|
|
39
|
+
|
|
40
|
+
This licence expressly includes the right to sublicense and distribute Your Contributions
|
|
41
|
+
**under any licence terms, including proprietary terms**, whether or not those terms are
|
|
42
|
+
the ones under which Subroutine is currently published. This is the clause that makes
|
|
43
|
+
commercial licensing of Subroutine possible, and it is the reason this document exists.
|
|
44
|
+
|
|
45
|
+
**3. Grant of patent licence.** You grant to the Project Owner, and to recipients of
|
|
46
|
+
software distributed by the Project Owner, a perpetual, worldwide, non-exclusive,
|
|
47
|
+
royalty-free, irrevocable patent licence to make, have made, use, offer to sell, sell,
|
|
48
|
+
import and otherwise transfer Subroutine. This applies only to those patent claims
|
|
49
|
+
licensable by You that are necessarily infringed by Your Contribution alone or by its
|
|
50
|
+
combination with Subroutine.
|
|
51
|
+
|
|
52
|
+
If any entity brings patent litigation alleging that Subroutine, or a Contribution
|
|
53
|
+
incorporated within it, constitutes direct or contributory patent infringement, any
|
|
54
|
+
patent licences granted to that entity under this agreement terminate as of the date the
|
|
55
|
+
litigation is filed.
|
|
56
|
+
|
|
57
|
+
**4. You have the right to grant this.** You represent that You are legally entitled to
|
|
58
|
+
grant the above licences, and that each of Your Contributions is Your original creation.
|
|
59
|
+
If Your employer has rights to intellectual property You create, You represent that You
|
|
60
|
+
have received permission to make the Contributions on behalf of that employer, that Your
|
|
61
|
+
employer has waived such rights, or that Your employer has executed a separate agreement
|
|
62
|
+
with the Project Owner.
|
|
63
|
+
|
|
64
|
+
**5. Work that is not Yours.** You may submit work that is not Your original creation
|
|
65
|
+
only if it is clearly identified as such, complete with its source and any licence or
|
|
66
|
+
other restriction attached to it. Mark it conspicuously as "Submitted on behalf of a
|
|
67
|
+
third party: [named here]".
|
|
68
|
+
|
|
69
|
+
**6. No warranty and no obligation.** You are not expected to provide support for Your
|
|
70
|
+
Contributions. Except for the representations in section 4, and unless required by
|
|
71
|
+
applicable law or agreed in writing, You provide Your Contributions on an "AS IS" basis,
|
|
72
|
+
without warranties or conditions of any kind, either express or implied, including
|
|
73
|
+
without limitation any warranty of title, non-infringement, merchantability or fitness
|
|
74
|
+
for a particular purpose.
|
|
75
|
+
|
|
76
|
+
**7. Tell us if something changes.** You agree to notify the Project Owner if You become
|
|
77
|
+
aware of any fact or circumstance that would make any of these representations inaccurate
|
|
78
|
+
in any respect.
|