asf-tooling commented on issue #912:
URL:
https://github.com/apache/tooling-trusted-releases/issues/912#issuecomment-4410061617
<!-- gofannon-issue-triage-bot v2 -->
**Automated triage** — analyzed at `main@2da7807a`
**Type:** `new_feature` • **Classification:** `actionable` •
**Confidence:** `high`
**Application domain(s):** `project_committee_management`,
`shared_infrastructure`
### Summary
Issue #912 describes a project cycle model (lifecycle tracking for version
lines) and project-level version metadata. The core implementation already
exists in the codebase: migration 0074 creates the `projectcycle` table and
adds version metadata fields to `project` and `cycle_key` to `release`;
`atr/cycles.py` implements cycle resolution; `atr/models/sql.py` defines
`VersionMethod` enum and the `ProjectCycle` relationship on `Project`; and
`atr/db/__init__.py` provides a `project_cycle()` query builder. Unit tests
exist. The data model described in the issue is fully implemented. Remaining
work may include UI forms for configuring version settings, automatic cycle
creation when releases are drafted, and EOD/EOS/EOL lifecycle automation.
### Where this lives in the code today
#### `migrations/versions/0074_2026.05.04_0d6e9554.py` — `upgrade` (lines
22-59)
_currently does this_
Database migration creating the projectcycle table and adding version
metadata columns to project/release tables, implementing the schema specified
in issue #912.
```python
def upgrade() -> None:
# project: add version-scheme metadata. Existing rows default to
"simple".
with op.batch_alter_table("project", schema=None) as batch_op:
batch_op.add_column(
sa.Column(
"version_method",
sa.Enum("SIMPLE", "SEMVER", "CALVER", name="versionmethod"),
nullable=False,
server_default="SIMPLE",
)
)
batch_op.add_column(sa.Column("version_pattern", sa.String(),
nullable=True))
batch_op.add_column(sa.Column("cycle_match", sa.String(),
nullable=True))
batch_op.add_column(sa.Column("branch_template", sa.String(),
nullable=True))
# projectcycle: create the table and backfill a "default" cycle for
every project.
op.create_table(
"projectcycle",
sa.Column("cycle_key", sa.String(), nullable=False),
sa.Column("cycle", sa.String(), nullable=False),
sa.Column("project_key", sa.String(), nullable=False),
sa.Column("start", sql.UTCDateTime(timezone=True), nullable=True),
sa.Column("begin", sql.UTCDateTime(timezone=True), nullable=True),
sa.Column("latest", sql.UTCDateTime(timezone=True), nullable=True),
sa.Column("eod", sql.UTCDateTime(timezone=True), nullable=True),
sa.Column("eos", sql.UTCDateTime(timezone=True), nullable=True),
sa.Column("eol", sql.UTCDateTime(timezone=True), nullable=True),
sa.Column("lts", sa.Boolean(), nullable=False,
server_default=sa.text("0")),
sa.ForeignKeyConstraint(
["project_key"],
["project.key"],
name=op.f("fk_projectcycle_project_key_project"),
ondelete="CASCADE",
),
sa.PrimaryKeyConstraint("cycle_key", name=op.f("pk_projectcycle")),
sa.UniqueConstraint("cycle_key",
name=op.f("uq_projectcycle_cycle_key")),
sa.UniqueConstraint("project_key", "cycle",
name="unique_project_cycle"),
)
```
#### `atr/cycles.py` — `cycle_name_for_version` (lines 37-61)
_currently does this_
Core cycle resolution logic that maps a version string to a cycle name using
the project's cycle_match regex pattern.
```python
def cycle_name_for_version(project: sql.Project, version: str) -> str:
"""Resolve which cycle a version belongs to.
Returns the cycle name (not the FK key). Projects with no `cycle_match`
set always return "default". Otherwise the regex is fullmatched against
the version string and capture-group 1 is returned.
Raises ValueError if the version doesn't match, the pattern has no
capture groups, or capture-group 1 captured the empty string.
"""
if project.cycle_match is None:
return _DEFAULT_CYCLE
match = re.fullmatch(project.cycle_match, version)
if match is None:
raise ValueError(f"Version {version!r} does not match cycle_match
for project {project.key!r}")
if not match.groups():
raise ValueError(f"cycle_match for project {project.key!r} has no
capture groups")
cycle = match.group(1)
if not cycle:
raise ValueError(f"cycle_match for project {project.key!r} captured
empty string from version {version!r}")
return cycle
```
#### `atr/models/sql.py` — `VersionMethod` (lines 244-247)
_currently does this_
Enum for the version_method field on Project, implementing the 'method'
column from the issue specification (semver, calver, simple).
```python
class VersionMethod(enum.StrEnum):
SIMPLE = "simple"
SEMVER = "semver"
CALVER = "calver"
```
#### `atr/models/sql.py` — `LifecycleEventType` (lines 262-268)
_currently does this_
Enum for lifecycle events matching the eod/eos/eol fields described in the
issue's cycle model.
```python
class LifecycleEventType(enum.StrEnum):
RELEASE = "release"
ARCHIVE = "archive"
WITHDRAW = "withdraw"
EOD = "eod"
EOS = "eos"
EOL = "eol"
```
#### `atr/db/__init__.py` — `Session.project_cycle` (lines 432-454)
_currently does this_
Query builder for ProjectCycle, enabling database lookups by cycle_key,
project_key, and cycle name with eager-loading options.
```python
def project_cycle(
self,
cycle_key: Opt[str] = NOT_SET,
project_key: Opt[str] = NOT_SET,
cycle: Opt[str] = NOT_SET,
_project: bool = False,
_releases: bool = False,
) -> Query[sql.ProjectCycle]:
query = sqlmodel.select(sql.ProjectCycle)
if is_defined(cycle_key):
query = query.where(sql.ProjectCycle.cycle_key == cycle_key)
if is_defined(project_key):
query = query.where(sql.ProjectCycle.project_key == project_key)
if is_defined(cycle):
query = query.where(sql.ProjectCycle.cycle == cycle)
if _project:
query = query.options(joined_load(sql.ProjectCycle.project))
if _releases:
query = query.options(select_in_load(sql.ProjectCycle.releases))
return Query(self, query)
```
#### `tests/unit/test_cycles.py` —
`test_cycle_name_extracts_capture_group_for_matching_version` (lines 30-32)
_currently does this_
Unit test verifying semver-style cycle extraction from version strings,
confirming the cycle resolution logic works as specified.
```python
def test_cycle_name_extracts_capture_group_for_matching_version():
project = SimpleNamespace(key="example",
cycle_match=r"^(\d+)\.\d+\.\d+$")
assert cycles.cycle_name_for_version(project, "2.5.3") == "2"
```
### Where new code would go
- `atr/get/cycles.py` — new file
A UI view for displaying and configuring project cycles does not appear to
exist yet. A GET route handler for viewing cycle settings would go here.
- `atr/post/cycles.py` — new file
A POST route handler for updating project version method, pattern,
cycle_match, and branch_template would go here (form submission to configure
cycles).
### Proposed approach
The data model for issue #912 is already fully implemented: the migration
creates the `projectcycle` table with all specified fields (cycle_key, cycle,
project_key, start, begin, latest, eod, eos, eol, lts), adds version metadata
to `project` (version_method, version_pattern, cycle_match, branch_template),
and links releases to cycles via `cycle_key`. The cycle resolution logic in
`atr/cycles.py` is tested and functional.
Remaining work likely includes: (1) UI forms for PMC members to configure
version_method/pattern/cycle_match/branch_template on their projects, (2)
automatic cycle creation when a release is drafted with a new cycle name
derived from cycle_match, (3) logic to update ProjectCycle.latest/start/begin
timestamps when releases progress through phases, and (4) EOD/EOS/EOL lifecycle
automation (archiving older releases when a cycle reaches end-of-life). Since
the core model is in place and the issue has no comments specifying further
work, additional diffs are speculative.
### Open questions
- Is the issue considered complete now that the data model and migration
exist, or are there additional UI/automation pieces being tracked under this
same issue?
- Should automatic cycle creation happen during release drafting (i.e., when
a version is first entered and cycle_name_for_version resolves a new cycle
name)?
- How should the eod/eos/eol datetime fields interact with release archival
- is there a scheduled task or is it triggered by a PMC member setting the date?
- The ProjectCycle model class definition is not visible in the truncated
sql.py file - it should exist there with fields matching the migration, but
confirmation would be good.
### Files examined
- `migrations/versions/0074_2026.05.04_0d6e9554.py`
- `atr/cycles.py`
- `atr/models/sql.py`
- `atr/db/__init__.py`
- `tests/unit/test_cycles.py`
- `atr/db/interaction.py`
- `atr/datasources/apache.py`
- `atr/registry.py`
### Related issues
This issue appears related to: #914.
_Both define project cycle and lifecycle event models for tracking release
information_
---
*Draft from a triage agent. A human reviewer should validate before merging
any change. The agent did not run tests or verify diffs apply.*
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]