今日已更新 84 条资讯 | 累计 37333 条内容
关于我们

标签:#Engineering

找到 546 篇相关文章

AI 资讯

Major Frontier Model Providers Adopt Watermarking Tech to Comply with EU Regulation

As of August 2, 2026, the EU AI Act Article 50 requires AI systems to mark synthetic outputs in a machine-detectable manner. Major vendors are implementing statistical watermarking methods, which influence natural language generation without affecting performance. This has prompted a swift reaction from the open-source community, raising compliance and vulnerability concerns. By Olimpiu Pop

2026-08-18 原文 →
AI 资讯

Five SQL Bugs That Never Threw an Error

A week cleaning 290 booking records taught me more about silent failure than any error message ever has Last week I cleaned a deliberately messy dataset; 290 booking records from Safari Connect, Nairobi bus platform, 21 columns, 23 catalogued data problems. Class exercise, but the data was built from real failure modes. The problems I'd been warned about took an afternoon. The ones that cost me were the five that ran perfectly, returned plausible output, and were wrong. Every one of these produced a result. None produced an error. 1. The date heuristic that silently dropped five bookings The dataset had three date formats in one column: 2024-09-15 , 15/09/2024 ,and 09-25-2024 . Two of those are ambiguous - 01-18-2024 is unmistakably MM-DD-YYYY because there's no month 18, but 04-10-2024 could be either. The supplied guide handled it like this: UPDATE bookings_staging SET departure_date = TO_DATE ( departure_date , 'MM-DD-YYYY' ):: TEXT WHERE departure_date LIKE '%-%' AND LENGTH ( departure_date ) = 10 AND SPLIT_PART ( departure_date , '-' , 2 ):: INTEGER > 12 ; Read that last condition. If the second component is too large to be a month,this must be month-first. Reasonable logic - and it only fires when the day happens to be 13 or higher. Five rows had days between 1 and 12. They never converted. Then the next step filtered on ISO format: INSERT INTO bookings SELECT ... FROM bookings_staging WHERE departure_date SIMILAR TO '[0-9]{4}-[0-9]{2}-[0-9]{2}' ; ...and dropped them. No error. No warning. Five completed bookings and KES 3,840 of revenue gone from every downstream total. The guide's expected row count was written as "~280+", which is loose enough to hide it. The fix is to match on shape, not to infer from values: WHERE departure_date ~ '^ \d {2}- \d {2}- \d {4}$' Anchored patterns are mutually exclusive, so you can classify every row before touching any of it: SELECT CASE WHEN departure_date ~ '^ \d {4}- \d {2}- \d {2}$' THEN 'ISO' WHEN departure_date ~ '^ \d

2026-08-18 原文 →
AI 资讯

Your Database Is Making 4 Promises. Here's What ACID Means.

Introduction Your program keeps opening transactions. A signup writes a new user row. A checkout debits one account and credits another. A form submission updates three related tables at once. You wrap it all in BEGIN and COMMIT and move on, trusting that the database will handle whatever happens in between. Most of the time it does. But what is it actually promising you when it handles that? And what does it have to do behind the scenes to keep that promise? Say a user transfers ₹1,000 from Account A to Account B. The application runs two updates: subtract 1,000 from A, add 1,000 to B. Now say the server crashes right after the first update runs but before the second one does. Account A: -₹1,000 Account B: +₹0 That money didn't move. It vanished. No error message fixes that, and no user accepts "the server restarted" as an explanation for their missing balance. This is the exact problem a set of guarantees called ACID was built to solve. Most developers can recite the acronym, Atomicity, Consistency, Isolation, Durability, without being able to explain what any of the four words actually promise, or what the database has to do internally to keep those promises. This article tries to fix that. -- 1. What Is a Transaction? Before ACID makes sense, you need to understand what a transaction actually is. A transaction is a group of one or more database operations treated as a single logical unit of work. Either the whole group succeeds, or none of it does. The bank transfer above is a textbook transaction: two updates that only make sense together. In SQL, a transaction usually looks like this: BEGIN ; UPDATE accounts SET balance = balance - 1000 WHERE id = 1 ; UPDATE accounts SET balance = balance + 1000 WHERE id = 2 ; COMMIT ; BEGIN tells the database "everything from here on is one unit." COMMIT tells it "we're done, make it permanent." If something goes wrong in between, a constraint violation, a crash, the application deciding to cancel, the database can issue a RO

2026-08-18 原文 →
AI 资讯

What are you working on? #01

What are you working on? I hear these words in my day-to-day. And sometimes, when I hear them, there’s this little brain freeze that happens because my brain is probably trying to put into words the amount of things that have wandered through my head in the last 24 hours. 😂 So I thought, okay, let me try something. I want to take some of those wandering thoughts, explorations, things I'm trying out and things I'm learning, and put them into writing. This is going to be a series where I come and talk about what I'm working on — software engineering, product, work, people, faith, relationships, rest, and whatever else happens to be taking up space in my head at the moment. So, what am I working on? I recently started writing backend code, and there’s a bit of a backstory to that. I built this frontend commerce store years ago where people can come and shop for furniture. At the time, I used a backend-as-a-service to handle the backend side of the application. Now, I’m coming back to that same system and writing the backend myself with NestJS. I wanted to go beyond just consuming a backend and actually understand what is happening behind the scenes. The learning process is a bit stretching at the moment because I’m getting familiar with a lot of new concepts. Tiring and frustrating? Yes. But the feeling when I finally understand the reason behind something is always refreshing. That has been really rewarding lately. I'm also in the middle of launching a mobile application at my workplace, going through system design classes, figuring out how to get the best out of my engineers (AI sub-agents, by the way 😅), and occasionally imagining that dream job where you get to build products that serve millions of people and work with really brilliant minds. Also, I discovered the productivity rush that comes with using large monitors. 😂 Then there's learning how to rest while also trying to close out all the open loops in my head. Building reading habits. Figuring out what to pri

2026-08-17 原文 →
AI 资讯

"Power Query Error: Formula.Firewall and Privacy Level Errors"

This isn't a syntax error or a bug in the query — it's Power Query's Formula Firewall refusing to combine data from more than one source until it knows whether that's actually safe. Combining a private/organizational source with a public one (an internal database and a public web API, for example) can leak data from one into the other; the firewall blocks it by default rather than guessing. Why This Exists Every data source in Power Query has a privacy level — Public, Organizational, or Private — set the first time it's connected to. When a query's steps end up needing to send data from one source into a call against a different source, Power Query checks whether the privacy levels involved allow that combination. Originally published on PBIDocs — Power BI documentation covering DAX, Power Query, data modeling, and Microsoft Fabric.

2026-08-16 原文 →
AI 资讯

How PGSimCity Turns PostgreSQL Complexity Into a Virtual City 3D Simulation

Nikolay Samokhvalov has developed PGSimCity, an open-source educational tool that visualises PostgreSQL mechanics as a 3D spatial simulation in the browser. It assists backend developers and site reliability engineers in understanding SQL and the dynamics of kernel execution. The project is available on GitHub and aims to enhance understanding of database architecture through interactive elements. By Olimpiu Pop

2026-08-16 原文 →
AI 资讯

'We'll fix it later' is a loan. Here's the interest rate

Every time someone on your team says "we'll clean it up later," they're taking out a loan. The problem is that almost nobody checks the interest rate — until it bankrupts an entire sprint. Technical debt is the most-used and least-understood metaphor in software. Used well, the metaphor is genuinely powerful, because debt is exactly the right mental model — including the part everyone forgets: interest. Debt isn't the same as bad code First, a correction. Technical debt isn't just messy or bad code. It's a deliberate or accidental trade: you took a shortcut — skipped the abstraction, hardcoded the value, deferred the test — to move faster now, in exchange for a cost later. Sometimes that's a smart, conscious decision. Shipping today to validate an idea, knowing you'll refactor if it works, is often the right call. The debt isn't the problem; unmanaged, invisible debt is. The interest is the point Here's what the metaphor gets exactly right and most teams ignore. Debt accrues interest . Every feature you build on top of a shortcut is a little harder to build. Every bug in the messy area takes a little longer to fix. The shortcut doesn't cost you once — it taxes every future change that touches it, and that tax compounds. This is why teams mysteriously slow down over time. It rarely feels like a wall; it feels like everything gradually getting harder, estimates creeping up, small changes turning into week-long ordeals. That's compounding interest on debt nobody tracked. I've watched a system's velocity get quietly reclaimed by exactly this, and paying it down deliberately is part of how I approach building things properly . Good debt, bad debt The framework that makes this actionable: Deliberate, prudent debt: "We know the right design, but we're shipping the simple version to hit the deadline, and we'll fix it." Fine — it's a conscious, tracked trade. Accidental, reckless debt: "What's a design pattern?" — debt taken on through inexperience, invisibly, with no plan t

2026-08-16 原文 →
AI 资讯

Context Is a Platform Capability Now

Watch a developer start an agent session on real enterprise work and you will see a ritual. Before the first useful prompt, they gather. They paste the deployment standard, link the runbook, and explain what the criticality tiers mean. Then they correct the agent's first confident guess about a naming convention the team retired two years ago. Tomorrow they will do it all again, because the agent will not remember. We have quietly decided that this gathering is the developer's job. Every guide to working with AI repeats some version of the same advice: give the model good context. So developers hunt for it, one session at a time, across systems that were never designed to answer an agent's questions. I think that framing is backwards, and I think fixing it is platform work. In Your Platform Has a New User: The Agent , I argued that internal platforms now serve two personas: the developer and the developer's agent. Near the end, I wrote that context is becoming part of the platform. I called it one of the most important developer experience problems of the next few years. That idea got four paragraphs. It deserves an essay, so here is the longer version. The gathering is the tax Agents can remember more than they used to. What they cannot reliably accumulate on their own is organizational truth. A new engineer pays the onboarding cost once, then amortizes it over years of context, hallway conversations, and scar tissue. An agent may retain instructions, memory, or project state. None of those automatically tell it which standard is authoritative, which exception still applies, or which decision was reversed six months ago. Whatever it needs to know about your organization still has to come from somewhere. Now multiply that across hundreds of engineers. People rediscover the same standards, fork the same repo, re-paste the same runbooks, and retype the same corrections, day after day. Quality varies too. Your strongest engineers assemble excellent context and get exce

2026-08-16 原文 →
AI 资讯

We Will Get You Through It!

There is a comedy sketch from Bob & Tom that starts with a hilariously impossible promise: overnight delivery by train, from New York to Los Angeles. At one point, someone asks if they can really get a 2,000-pound package across the country overnight by rail. The answer is delivered with absolute confidence: “Norfolk and Waypal, overnight. Absolutely. Positively.” The name is doing some careful work. It lets you hear the phrase that nobody has actually said out loud. No way, pal. When I end up leading a project with six weeks left and something that feels like four months of work to do, I start the internal kickoff by telling the team to go watch that sketch. No other explanation. Just go watch it, then come back. Then I tell them: “Absolutely, positively, we will get you through it. There's Norfolk and Waypal, we are gonna to do it.” That does not mean we are going to do the thing exactly as it was originally promised. It means we are going to get through it. Absolutely. Positively. There is a difference. Laugh at the impossible first I think newer developers especially need permission to laugh at impossible requirements. An 800-pound gorilla from New York to Los Angeles overnight by train is impossible in a way that is easy to laugh at. A project that needs a full cloud environment, API work, a mobile application in the app stores, production deployment, security approvals, and a dozen other things in six weeks? That can feel less funny when it is sitting in your sprint board. But it may be just as impossible if we take the requirements literally. The first danger on a crunch project is shame. A junior developer can look at an impossible deadline and wonder if they are missing something. Maybe everyone else understands how this gets done. Maybe it is a talent problem. Maybe if they just worked harder, they could turn six weeks into twelve. Nope. Sometimes the work is just Norfolk and Waypal . Humor does not solve the problem. It lowers the temperature enough that

2026-08-16 原文 →
AI 资讯

Your `if` statements are a database nobody can query

Somewhere in your codebase there is a line that looks like this: if ( user . plan === ' enterprise ' || user . tenantId === ' acme-corp ' ) { // ... } Nobody remembers who wrote the second half of that condition. It has been there for two years. It is almost certainly still load-bearing. Here is the thing I want to convince you of: that line isn't code. It's data, and it's stored in the worst possible place. Every conditional that encodes a business decision is really a row. It has a condition, an outcome, and a bunch of implicit context about when it applies. You have hundreds of these rows. They're spread across a dozen services, written in four different styles, and there is no way to list them. You have a database. You just can't query it. Five things a database gives you that your code doesn't Once you look at it this way, the problems stop feeling like sloppiness and start feeling structural. There's no schema. One service decides a customer is premium by checking plan === 'premium' . Another checks subscription.tier > 2 . A third checks a flag that was set during a migration in 2023. All three are "the same rule" until the day they aren't, and there's nothing in the system that would notice the drift. There's no way to query it. Try to answer a simple question: what rules are live in production right now? You can't. Someone has to read the source. And grep won't save you, because the interesting conditions are compound, spread across guard clauses, and half of them are expressed as an early return rather than an if . There are no migrations. Changing a rate limit from 100 to 200 requires a pull request, a review, a CI run, and a deploy window. You're pushing a code change through the full pipeline to change a number. It's a schema migration with none of the tooling that makes schema migrations tolerable. There's no audit log. Git tells you who edited the line. It doesn't tell you who decided the rule, when it was supposed to expire, or whether the customer it

2026-08-15 原文 →
AI 资讯

I Reverse-Engineered a Restaurant ERP With No Documentation. Here's What It Taught Me About Being a Self-Taught Developer.

There is no manual for TronSoft. No API reference, no schema diagram, no forum thread explaining why a comanda refuses to close. If you want to understand it, you open the database and start pulling threads until something makes sense. That's exactly what I did — for months, on top of my actual job. The problem nobody wrote down I'm the Operations Manager at a restaurant in Itaúna, a mid-sized town in Minas Gerais, Brazil. I'm also the only person there who writes software. Not because I was hired to — because the restaurant runs on a Brazilian ERP called TronSoft, built on a Firebird database, and Firebird doesn't come with the kind of ecosystem you get around Postgres or MySQL. No Stack Overflow flood of answers. No official docs beyond a thin operator manual. Vendor support exists, but it's slow, and it doesn't scale to "I want to automate this specific internal workflow at 11pm on a Tuesday." So when I needed to automate payment reconciliation, close out comandas without touching the vendor's fragile UI, and trigger fiscal document emission (NFC-e) reliably, I didn't have a spec to follow. I had a live production database and a lot of curiosity. Learning a system by watching it think I started the way you'd expect: opening tables, guessing at relationships, breaking things in a test environment until I understood why they broke. Over time that turned into something more systematic — I ended up documenting 390 tables and 514 foreign keys across roughly 40 functional modules, entirely from observation. No vendor documentation, no source code access. Just structure, inference, and a lot of trial and error. Some of what I learned only reveals itself under pressure: Firebird's SQL dialect has its own quirks — FIRST 1 instead of LIMIT , for one. Small thing, but it breaks every query you copy-paste from a Postgres tutorial. Primary keys aren't auto-incrementing in the way you'd assume. They're driven by generators ( GEN_ID ), and if you write a record without syncing

2026-08-15 原文 →
开发者

The Fix Was Committed. The Old Value Kept Running.

Originally published on hexisteme notes . I deleted three ambient API keys from my shell profile. Then I ran the standard clean-room check — spawn a shell with no inherited environment at all, env -i HOME="$HOME" /bin/zsh -lc 'echo "${VARNAME:-unset}"' , and read unset back for all three. That command doesn't lie: a shell started with an empty environment can only see what the current profile puts there, so if it reports the variable missing, the profile is clean. I closed the loop, reconnected my tools, and moved on. Minutes later I reconnected a review tool I run for cross-vendor sanity checks, and it came back healthy — with eight providers registered, one of them authenticated with a key I had just deleted. Not a cached credential from an old response. A live, working authentication, using a value that no longer existed anywhere on disk. The fix was committed. The old value kept running. Two different questions that sound like one "Did I fix the config?" and "Is the fix in effect?" collapse into a single question in your head, because in the common case they're the same event: you edit a file, the next thing that reads the file gets the new value, done. env -i answers the first question perfectly. It says nothing about the second, because it doesn't test any process that already exists — it only tests a brand-new one, freshly spawned, that has no choice but to read the current profile because it has no environment of its own yet. Every process that was already running before you made the edit is a different story. It read the profile once, at its own startup, copied whatever it found into its own memory, and has not looked at the file since. From that point forward it is not a reader of your shell profile — it is a cache of it. And caches don't invalidate themselves. Finding the actual culprit The process holding the stale value here was the editor I was working in — the same long-lived process that hosts my coding sessions and manages tool connections through M

2026-08-15 原文 →
AI 资讯

My Job Hasn't Changed. My Day Has.

Times are changing, my role is changing, my focus is changing, my impact is changing. But in essence – I'm still doing the same. I still build products that drive impact. Only my day-to-day looks completely different. The shift is happening, sooner or later, if you want it or not. Whether or not you can cope, is all up to you. In the past, I was neck-deep in code. That was what the majority of my time consumed. I liked it, building things, building products. These days, that's all done by an endless amount of AI agents. I barely touched any code in the past half year – if not even longer. My focus moved from building products to building my own process The work that used to go into a feature now goes into the process that produces the feature. Instead of losing the first hour of my day to Slack and email, I built a small stack of scheduled agents that hand me a briefing before I even open my laptop ( already wrote about that one ). Instead of reading every pull request line by line, I set up a review loop where agents do the first pass and I stay on the hook for whatever they flag. None of it started as a plan. Each piece started as one specific annoyance I got tired of and fixed. That's the actual mechanism: improve one small thing, it saves you time, you reinvest that time into the next small improvement. Compounding, not a grand strategy. The question I try to ask myself daily is simple: how can I do my job a bit better today than I did it yesterday? Not more. Not faster. Better. I also don't run ten parallel AI workflows across different projects at the same time because someone told me that's what a serious AI-software engineer does now. If I have multiple projects going on, I only focus on one project at a time. That's the amount of mental space I have right now, and I've stopped treating that as a shortcoming. My impact shifted from writing code to making my team better The time that used to go into implementation didn't disappear, it moved upstream. I now sp

2026-08-14 原文 →
AI 资讯

Before You Merge AI-Generated Code, Ask These 12 Questions

I've merged plenty of AI-generated code that was genuinely fine. I've also caught myself almost merging code that looked fine and wasn't, because it read like something a competent person wrote and my brain filled in the rest. Over the last year I've settled into a rough set of questions I run through before approving anything I didn't write line by line myself, generated or not. Here they are, in the order I actually ask them. 1. What problem is this code actually solving? It's easy to review whether code works and skip whether it solves the right thing. AI tends to answer the literal prompt, not the intent behind it. def get_active_users (): return db . query ( " SELECT * FROM users WHERE active = true " ) If "active" was supposed to mean "logged in within 30 days" and not a boolean flag that's rarely updated, this passes every test and still solves the wrong problem. Reviewer tip: Read the original ticket or request before reading the diff. Check the code against the intent, not just the literal ask. 2. Do I actually understand the implementation? Not "does it look reasonable," actually understand it, line by line, well enough to explain it to someone else. Reviewer tip: Try to explain the function out loud in one sentence per major step. If you get stuck anywhere, that's the part you haven't actually reviewed yet, just skimmed. 3. What assumptions is it making? Every implementation bakes in assumptions about the shape of the data, the order things happen in, or what "normal" looks like. function getLatestOrder ( orders ) { return orders [ orders . length - 1 ]; } This assumes orders is sorted chronologically and never empty. Neither assumption is stated anywhere. Reviewer tip: Ask "what does this assume about its inputs that isn't checked anywhere?" Write the answer down, literally, in the PR comment if it matters. 4. What happens with bad input? Bad input isn't an edge case, it's a certainty over a long enough timeline. def parse_age ( value ): return int ( val

2026-08-14 原文 →
AI 资讯

Meta Open-Sources Muse Glimmer: A 30B Local Agentic Model Optimised for On-Device Execution

Meta AI Research has introduced Muse Glimmer, a 30-billion-parameter open-weight model under the Apache 2.0 license, designed for local workflows. It enables autonomous agents and complex task execution on consumer GPUs without relying on cloud APIs. The model employs a multi-stage training approach for efficient performance and supports multimodal inputs, enhancing coding and automation tasks. By Olimpiu Pop

2026-08-14 原文 →
AI 资讯

loveyourclanker.org

I created an open web resource for Software Engineers. https://loveyourclanker.org/ It highlights different patterns we can consciously choose use when interacting with our AI Coding tools (a.k.a 'Agents'... a.k.a 'Clankers') to stay in control, maintain quality and sensibly increase efficiency. I was prompted to do this (no pun intended) by observing some pretty alarming signals coming from this community. Token leaderboards, engineers being encouraged to use tools to "stay current" or "keep up" or "not be redundant", engineers quitting tools entirely to stay sane, engineers leaving social gatherings to get back to their agents, engineers setting up whole systems that automate away human engineers and then calling that "agentic engineering". I'm hoping that if we normalise and share how we use the tools, and show that there are different ways where you maintain more control and agency (... pun?) that it might promote a better If you find it helpful, share. If you disagree or want to contribute, raise a PR or ping me. It's all open and NFP.

2026-08-14 原文 →
AI 资讯

Reflecting on 7-8 Years of Career Growth: Adaptability and Continuous Learning Key to Senior Data Engineer Success

Analytical Insights: The Mechanisms Driving Career Growth in Data Engineering In the rapidly evolving field of data engineering, career progression is not merely a product of time served but a result of deliberate, adaptive strategies. A 7-8 year trajectory to a Senior Data Engineer role, marked by multiple successful contracts, underscores the critical role of adaptability and continuous learning. This analysis dissects the mechanisms that propel career growth, highlighting their interdependencies and the consequences of their neglect. 1. Continuous Learning and Skill Development Impact: The pace of technological advancement in data engineering demands constant upskilling. Internal Process: Engaging with new tools, methodologies, and industry trends through online courses, certifications, and hands-on practice ensures relevance. Observable Effect: Enhanced technical proficiency translates into the successful delivery of complex projects and the attainment of senior-level roles. Instability: Skill Stagnation occurs when learning efforts are inconsistent or outdated, leading to reduced competitiveness. This gap between current skills and industry demands can halt career progression, making individuals less attractive to employers seeking cutting-edge expertise. Intermediate Conclusion: Continuous learning is not optional; it is a survival mechanism in a field where obsolescence is a constant threat. 2. Client Relationship Management Impact: Diverse client needs and expectations across multiple contracts require tailored approaches. Internal Process: Implementing tailored communication strategies, proactively aligning project goals, and establishing iterative feedback loops foster trust and collaboration. Observable Effect: High client satisfaction leads to repeat contracts and positive referrals, which are critical for career advancement. Instability: Client Misalignment arises from inadequate communication or misunderstanding of client requirements, resulting in pro

2026-08-14 原文 →
AI 资讯

Design Notes for a Deterministic C++ Simulation Framework

“Same inputs, same result” sounds like a simple requirement. In a multithreaded simulation, it is an architectural constraint that touches data layout, scheduling, physics, randomness, floating-point behavior, serialization, and debugging. Determinism is valuable for replays, lockstep networking, regression tests, and reproducing hard failures. It does not happen automatically. Define the determinism boundary Start by stating what must match. Do two runs on the same executable and machine need identical results? Across different compilers? Across CPU architectures? Across operating systems? Those are increasingly difficult guarantees. A framework should document the supported boundary rather than using “deterministic” as a universal adjective. Control time Do not feed variable wall-clock deltas directly into a deterministic simulation. Use a fixed simulation step and decide how the renderer catches up or interpolates. Record inputs by simulation tick. If the system pauses or falls behind, handle that condition explicitly instead of silently changing the rules. Make randomness replayable Every pseudorandom decision needs a known generator, seed, and consumption order. A global generator shared by many systems is fragile because adding one random call in an unrelated feature shifts the sequence everywhere. Prefer scoped streams or deterministic derivation by system, entity, and tick where appropriate. Record seeds in test and replay artifacts. Schedule parallel work deliberately Multithreading introduces nondeterministic execution order. If two jobs write shared state, results may depend on timing even when data races are technically avoided. A robust job graph should make read and write sets visible, separate independent phases, and define deterministic merge or reduction rules. Avoid relying on thread completion order. Parallelize work whose outputs can be combined predictably. Keep entity iteration stable Entity-component systems often use dense arrays and swap-rem

2026-08-14 原文 →
AI 资讯

What Permit Files Can Teach Us About Reliable Workflow Software

Paperwork-heavy workflows rarely fail because a database cannot store another PDF. They fail because the system loses the relationship between the document, the real-world object, the decision it supports, and the stage of work it represents. Permits provide a useful example. A complete project record is not one uploaded form. It is an evidence chain that changes over time. A recent Local Service Ledger guide to Pasco County septic-repair records organizes the file into eight stages: property, existing system, site, pump-out, water and sewer, application, permit, and closeout. The guide's most important software lesson is that a receipt or contractor proposal alone does not establish the complete chain from reported problem to final recorded status. That distinction generalizes well beyond permits. 1. Give every workflow a stable subject Every document should attach to a stable entity: a property, customer, asset, case, project, or account. Do not rely on a filename or free-form address as the only identifier. Normalize enough data to prevent obvious duplication, preserve the source value, and retain a stable internal ID. For a property workflow, several records may contain slightly different owner names or address formatting. The system should help a reviewer determine whether they refer to the same site without silently overwriting those differences. 2. Separate observations, proposals, and decisions These are different kinds of facts: an owner reports a symptom; a contractor proposes a scope; an authority authorizes specific work; an inspector records a result; a final status closes the file. Collapsing them into one “project description” field destroys provenance. Model the actor, date, source, and status of each statement. The interface can display the current operational summary while preserving the earlier language that explains how the record evolved. 3. Make state transitions explicit A reliable workflow should not infer completion because a document exists

2026-08-14 原文 →