Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .changes/github/0.2.0.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
## 0.2.0 - 2026-08-24
### Added
* Synchronize the authenticated account's Stars, Lists, memberships, and repository ownership as reusable graph facts.
5 changes: 4 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ Reason in English. Communicate with humans in Chinese. Call the user “Sir”.
- Expensive core-py internal design: `docs/30-unit-tdd/`
- Runtime, packaging, migration, observability, and recovery: `docs/40-deployment/`
- Vulnerability reporting: `SECURITY.md`; local security boundaries: `docs/30-unit-tdd/security-model.md`
- Volatile task control: `tasks/`; never treat it as durable truth
- Volatile task control: `tasks/`; never treat it as durable truth, but retain an active packet until its parent task closes
- Mechanically enforceable facts: code, configuration, schemas, tests, assertions, lint, and CI
- Repeated subtree hazards only: the nearest local `AGENTS.md`

Expand All @@ -24,9 +24,12 @@ Resolve the semantic owner before adding durable material. A Unit is a logical r
and [contribution workflow](https://github.com/InKCre/.github/blob/main/CONTRIBUTING.md) for branches, pull requests,
release authority, and delivery boundaries; repository-local documents own exact commands.
- Before a reference-sensitive, logic-altering, or non-obviously-local durable mutation, state the exact object, `From -> To`, side effects, blast radius, invariants, verification, and uncertainty.
- Before promoting behavior, evaluate delivery owner, durable owner, interface layer, and external capability owner independently. Importance, first-party distribution, current pressure, or successful acceptance on one axis does not prove another.
- Before owning external protocol mechanics, inspect existing dependencies and primary documentation and name the unsupported gap. Keep only the application-specific remainder.
- Read the nearest local `AGENTS.md` before changing its subtree. Read shared Product or Product TDD only when that owner is implicated, then the relevant local Unit TDD or Deployment document.
- Before a security-sensitive claim, read the security model and name actor, capability, asset, boundary, harm, and attack path. Missing defense in depth is hardening unless evidence shows a boundary violation.
- Exclude `tasks/`, generated output, dependencies, environments, caches, and temporary directories from ordinary source and durable-doc search unless they are the evidence target.
- Clean task artifacts by parent-task lifecycle, not directory class, age, size, or completed child units. Splitting content must not create a second control authority.
- Use sub-agents only when bounded isolation or parallel capacity repays assignment, validation, integration, conflict, and residual cost. Primary owns the Human relationship, global integration, and material residual.

Pause for Human input when the requested change conflicts with Product/Technical truth, ownership across durable surfaces remains unclear, evidence cannot support a bug or architecture decision, or the shortcut would damage maintainability.
Expand Down
19 changes: 19 additions & 0 deletions app/business/info_base/block.py
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,25 @@ def create(cls, form: BlockForm, db_session: Opt[sqlmodel.Session] = None) -> Bl
)
return block

@classmethod
def create_many(
cls,
forms: typing.Iterable[BlockForm],
db_session: sqlmodel.Session,
) -> tuple[BlockModel, ...]:
"""Create a caller-owned batch with one persistence round trip.

The caller owns the surrounding transaction. Returned models have their
database-managed identities populated, but are not individually refreshed.
"""
blocks = tuple(_new_block(form) for form in forms)
if not blocks:
return ()
logger.info("Creating block batch", extra={"block_count": len(blocks)})
db_session.add_all(blocks)
db_session.flush()
return blocks

@classmethod
async def fetchsert(cls, form: BlockForm, db_session: sqlmodel.Session) -> BlockModel:
"""Create if not exists, else return the existing one.
Expand Down
17 changes: 16 additions & 1 deletion app/business/info_base/relation.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from app.engine import SessionLocal
from libs.obsrv.main import get_logger
from app.schemas.info_base.block import BlockID
from app.schemas.info_base.relation import RelationModel
from app.schemas.info_base.relation import RelationCreateForm, RelationModel
from app.schemas.info_base.relation import RelationID
from utils.types_ import Undefined, _undefined

Expand Down Expand Up @@ -122,6 +122,21 @@ def create(
)
return relation

@classmethod
def create_many(
cls,
forms: typing.Iterable[RelationCreateForm],
db_session: sqlmodel.Session,
) -> tuple[RelationModel, ...]:
"""Create a caller-owned batch with one persistence round trip."""
relations = tuple(RelationModel.model_validate(form) for form in forms)
if not relations:
return ()
logger.info("Creating relation batch", extra={"relation_count": len(relations)})
db_session.add_all(relations)
db_session.flush()
return relations

@classmethod
def fetchsert(
cls, relation: RelationModel, db_session: sqlmodel.Session
Expand Down
28 changes: 6 additions & 22 deletions app/business/source/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from typing import Optional as Opt

from app.engine import SessionLocal
from app.database_contract.profile import BUILTIN_SOURCE_TYPES_BY_ID
from app.business.info_base.block import BlockManager
from app.schemas.info_base.block import BlockForm, BlockModel
from app.schemas.job import JobModel
Expand Down Expand Up @@ -168,30 +167,15 @@ def sync_source_types(
registered = cls._SOURCE_CLASSES if source_classes is None else source_classes
with SessionLocal() as db:
for source_type, source_cls in registered.items():
builtin = BUILTIN_SOURCE_TYPES_BY_ID.get(source_type)
stmt = sqlalchemy.dialects.postgresql.insert(SourceTypesModel).values(
id=source_type,
description=(
builtin.description
if builtin is not None
else source_cls.__doc__ or "No description."
),
config_schema=(
builtin.config_schema if builtin is not None else source_cls.__configschema__
),
collect_config_schema=(
builtin.collect_config_schema
if builtin is not None
else source_cls.__collectconfigcls__.model_json_schema()
),
description=source_cls.__doc__ or "No description.",
config_schema=source_cls.__configschema__,
collect_config_schema=source_cls.__collectconfigcls__.model_json_schema(),
backfill_config_schema=(
builtin.backfill_config_schema
if builtin is not None
else (
None
if source_cls.__backfillconfigcls__ is None
else source_cls.__backfillconfigcls__.model_json_schema()
)
None
if source_cls.__backfillconfigcls__ is None
else source_cls.__backfillconfigcls__.model_json_schema()
),
)
stmt = stmt.on_conflict_do_update(
Expand Down
30 changes: 0 additions & 30 deletions app/database_contract/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@
from .profile import (
BUILTIN_AI_DIALECTS,
BUILTIN_JOB_TYPES,
BUILTIN_SOURCE_TYPES,
BUILTIN_STORAGES,
BUILTIN_STORAGE_TYPES,
)
Expand Down Expand Up @@ -119,35 +118,6 @@ def reconcile_builtins(database_url: str | None = None) -> None:
),
)

for source_type in BUILTIN_SOURCE_TYPES:
cursor.execute(
sql.SQL(
"""
INSERT INTO {}.sources_types (
id, description, config_schema, collect_config_schema,
backfill_config_schema
)
VALUES (%s, %s, %s, %s, %s)
ON CONFLICT (id) DO UPDATE
SET description = EXCLUDED.description,
config_schema = EXCLUDED.config_schema,
collect_config_schema = EXCLUDED.collect_config_schema,
backfill_config_schema = EXCLUDED.backfill_config_schema
"""
).format(sql.Identifier(PROTOCOL_SCHEMA)),
(
source_type.id,
source_type.description,
Jsonb(source_type.config_schema),
Jsonb(source_type.collect_config_schema),
(
None
if source_type.backfill_config_schema is None
else Jsonb(source_type.backfill_config_schema)
),
),
)

for storage in BUILTIN_STORAGES:
cursor.execute(
sql.SQL(
Expand Down
Loading
Loading