BE-PR-010: deprecate feat_task_id — retain column, read-only compat

- Updated model docstring with full deprecation strategy
- Updated column comment to mark as deprecated (BE-PR-010)
- Updated schema/router comments for deprecation clarity
- Added deprecation doc: docs/BE-PR-010-feat-task-id-deprecation.md
- feat_task_id superseded by Task.source_proposal_id (BE-PR-008)
This commit is contained in:
zhi
2026-03-30 12:49:52 +00:00
parent 08461dfdd3
commit 90d1f22267
4 changed files with 87 additions and 12 deletions

View File

@@ -51,7 +51,7 @@ def _serialize_proposal(db: Session, proposal: Proposal, *, include_essentials:
"project_id": proposal.project_id,
"created_by_id": proposal.created_by_id,
"created_by_username": creator.username if creator else None,
"feat_task_id": proposal.feat_task_id, # DEPRECATED — read-only compat
"feat_task_id": proposal.feat_task_id, # DEPRECATED (BE-PR-010): read-only for legacy rows. Clients should use generated_tasks.
"created_at": proposal.created_at,
"updated_at": proposal.updated_at,
}
@@ -237,7 +237,7 @@ def update_proposal(
raise HTTPException(status_code=403, detail="Proposal edit permission denied")
data = proposal_in.model_dump(exclude_unset=True)
# Never allow client to set feat_task_id
# DEPRECATED (BE-PR-010): feat_task_id is read-only; strip from client input
data.pop("feat_task_id", None)
for key, value in data.items():
@@ -365,7 +365,7 @@ def accept_proposal(
})
next_num = task.id + 1 # use real id for next code to stay consistent
# Update proposal status (do NOT write feat_task_id — deprecated)
# Update proposal status feat_task_id is NOT written (deprecated per BE-PR-010)
proposal.status = ProposalStatus.ACCEPTED
db.commit()

View File

@@ -24,11 +24,21 @@ class Proposal(Base):
one Project.
- ``created_by_id`` — FK to ``users.id``; the user who authored the Proposal.
Nullable for legacy rows created before tracking was added.
- ``feat_task_id`` — **DEPRECATED**. Previously stored the single generated
``story/feature`` task id on accept. Will be replaced by
the Essential → story-task mapping (see BE-PR-008).
Kept in the DB column for read-only backward compat; new
code MUST NOT write to this field.
- ``feat_task_id`` — **DEPRECATED (BE-PR-010)**. Previously stored the single
generated ``story/feature`` task id on old-style accept.
Superseded by the Essential → story-task mapping via
``Task.source_proposal_id`` / ``Task.source_essential_id``
(see BE-PR-008).
**Compat strategy:**
- DB column is RETAINED for read-only backward compatibility.
- Existing rows that have a value will continue to expose it
via API responses (read-only).
- New code MUST NOT write to this field.
- Clients SHOULD migrate to ``generated_tasks`` on the
Proposal detail endpoint.
- Column will be dropped in a future migration once all
clients have migrated.
"""
__tablename__ = "proposes" # keep DB table name for compat
@@ -60,11 +70,14 @@ class Proposal(Base):
comment="Author of the proposal (nullable for legacy rows)",
)
# DEPRECATED — see class docstring. Read-only; will be removed once
# Essential-based accept (BE-PR-007 / BE-PR-008) is fully rolled out.
# DEPRECATED (BE-PR-010) — see class docstring for full compat strategy.
# Read-only; column retained for backward compat with legacy rows.
# New accept flow writes Task.source_proposal_id instead.
# Will be dropped in a future schema migration.
feat_task_id = Column(
String(64), nullable=True,
comment="DEPRECATED: id of the single story/feature task generated on old-style accept",
comment="DEPRECATED (BE-PR-010): legacy single story/feature task id. "
"Superseded by Task.source_proposal_id. Read-only; do not write.",
)
created_at = Column(DateTime(timezone=True), server_default=func.now())

View File

@@ -297,7 +297,7 @@ class ProposalResponse(ProposalBase):
project_id: int
created_by_id: Optional[int] = None
created_by_username: Optional[str] = None
feat_task_id: Optional[str] = None # DEPRECATED — will be removed after BE-PR-008
feat_task_id: Optional[str] = None # DEPRECATED (BE-PR-010): legacy field, read-only. Use generated_tasks instead.
created_at: datetime
updated_at: Optional[datetime] = None