Complete Projects API lifecycle and field validation #80

Closed AtlantisPleb opened this 23h ago 1 comment

Outcome

Complete the project and project-field lifecycle so project views can serve as durable planning surfaces.

Current behavior

The API can create and read projects and create fields. It cannot update, close, reopen, archive, or delete projects, and stored field names, option identifiers, data types, and values do not yet provide a complete validated contract.

Source: docs/2026-08-21-issues-projects-work-system-assessment.md, tracks C3 and C4.

Contract

  • Add project update, close, reopen, archive, and policy-controlled delete operations.
  • Add actor-attributed project lifecycle events.
  • Validate supported field data types, unique names, stable option identifiers, options, and item values at database and application boundaries.
  • Define how field changes affect existing item values.
  • Preserve repository authority and private-project non-disclosure on every route.
  • Publish the routes and schemas through the API contract.
  • Keep project items as references to canonical issues.

Acceptance criteria

  • Authorized users can update and transition a project through web and API.
  • Unauthorized callers cannot infer private project existence or mutation results.
  • Invalid, duplicate, stale, or destructive field changes return stable errors and preserve data.
  • Lifecycle and field changes appear in project activity.
  • Existing project and issue clients remain compatible.

Verification

Write controller and context tests first, then migration, authorization, event, rollback, and exact-candidate API tests. Run mix precommit.

Dependencies

Build on #9 and #60. Coordinate the item and stored-board issue in project 8.

  1. AtlantisPleb opened this issue 23h ago
  2. A AtlantisPleb Author 6h ago

    Shipped in b315cb2 on main.

    Endpoints now complete

    Method Path
    PATCH /repos/{owner}/{repo}/projectsV2/{project_number} — now also accepts archived
    DELETE /repos/{owner}/{repo}/projectsV2/{project_number}
    PATCH /repos/{owner}/{repo}/projectsV2/{project_number}/fields/{field_id}
    DELETE /repos/{owner}/{repo}/projectsV2/{project_number}/fields/{field_id}

    GET /repos/{owner}/{repo}/projectsV2 gains an archived=true parameter, and a project object now reports archived and archived_at.

    Lifecycle

    A project moves along two independent axes. state stays open or closed, so close and reopen are PATCH on state. Archive and restore are PATCH on archived, backed by an archived_at timestamp. Closing says the work the board tracked reached an end; archiving says the board left the working set, whatever became of the work. Keeping the archive off state leaves every existing reader of open and closed — the API, the board, the workspace tabs — reading the two values it always read. Project lists omit archived boards until asked.

    DELETE on a project needs two keys: a writable membership in the repository, and a project already archived. The board pairs its delete control with a confirmation prompt and an API caller has none, so archiving is the deliberate step that stands in for one. Deleting removes the project's fields and items, never the referenced issues, and never the append-only item event history.

    Validation

    • state is checked by validate_inclusion/3 and by a new projects_state_check database constraint.
    • A field's data_type is text, number, date, single_select, or promise_state, checked in the changeset and by a project_fields_data_type_check database constraint.
    • Field names are unique within a project, compared without case, enforced by a unique index on (project_id, lower(name)). The name is the key an item stores its value under, so a duplicate would make a stored value ambiguous.
    • A select carries a non-empty options.values list. Entries are names, where the name identifies the option, or objects with a stable id and name, where the identifier survives a relabel. Duplicate identifiers, blank entries, and options on a data type that carries none are all refused.
    • Item values are checked against the fields a project declares: a select value must name one of the field's options, a number must be a number, a date must be ISO 8601. A value under a key no field declares passes through untouched, so a board keeps a value written before its field existed and existing clients stay compatible.

    How a field change affects existing item values

    • Rename rewrites the stored key on every item of the project in the same transaction, so a rename never empties the column.
    • Data type never changes. Values already stored were written against the old type, and reinterpreting them is a destructive change wearing an edit's clothes.
    • Dropping an option items still carry returns 422 naming the identifiers in use, and preserves the options and the values.
    • Deleting a field items still carry returns 422 and preserves both the field and the values.

    Activity and authority

    Every project lifecycle and field change appends an actor-attributed activity entry, readable at .../notes?kind=activity. The new routes resolve the repository from the path and require a writable membership, so a non-member receives 404 and a private repository's projects and fields stay indistinguishable from missing ones.

    Verification

    Controller and context tests were written before the routes, controller, context, and migrations, per the repository's test-first workflow. MIX_TEST_PARTITION=lane80 mix precommit passes: 3072 tests, 15 excluded.

    Documented in docs/github-api-issues-projects-assessment.md and priv/docs/projects.md.

    Left to #81

    Item operations and stored-field board rendering stay out of scope. Two shapes established here are what that lane builds on: ProjectField.option_ids/1 and ProjectField.option_id/1 are the public readers for what a select offers, and an item stores the option identifier rather than the label.

  3. closed this as completed 6h ago
Sign in with GitHub to comment on this issue.